diff --git a/.circleci/config.yml b/.circleci/config.yml index 73803ea12f..5eb4ec2ce1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ version: 2.1 # Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects. orbs: android: circleci/android@1.0.3 - codecov: codecov/codecov@1.2.0 + codecov: codecov/codecov@3.2.4 jobs: # Below is the definition of your job to build and test your app, you can rename and customize it as you want. @@ -45,4 +45,4 @@ workflows: # For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows dotests: jobs: - - build-and-test \ No newline at end of file + - build-and-test diff --git a/app-wear-shared/shared/build.gradle b/app-wear-shared/shared/build.gradle index 9228d00a27..e277f8b1c4 100644 --- a/app-wear-shared/shared/build.gradle +++ b/app-wear-shared/shared/build.gradle @@ -34,7 +34,7 @@ dependencies { api 'org.slf4j:slf4j-api:1.7.36' // 2.0.x breaks logging. Code change needed api 'com.github.tony19:logback-android:2.0.0' - api "org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version" + api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinx_serialization_version" api "org.apache.commons:commons-lang3:$commonslang3_version" //RxBus diff --git a/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/EventData.kt b/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/EventData.kt index a1c2fecced..59c8bc6fa7 100644 --- a/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/EventData.kt +++ b/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/EventData.kt @@ -3,6 +3,7 @@ package info.nightscout.rx.weardata import info.nightscout.rx.events.Event import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json +import org.joda.time.DateTime import java.util.Objects @Serializable @@ -90,6 +91,16 @@ sealed class EventData : Event() { @Serializable data class ActionQuickWizardPreCheck(val guid: String) : EventData() + @Serializable + data class ActionHeartRate( + val duration: Long, + val timestamp: Long, + val beatsPerMinute: Double, + val device: String): EventData() { + override fun toString() = + "HR ${beatsPerMinute.toInt()} at ${DateTime(timestamp)} for ${duration / 1000.0}sec $device" + } + @Serializable data class ActionTempTargetPreCheck( val command: TempTargetCommand, diff --git a/app-wear-shared/shared/src/main/java/info/nightscout/shared/extensions/PackageManagerExtension.kt b/app-wear-shared/shared/src/main/java/info/nightscout/shared/extensions/PackageManagerExtension.kt index 003236cd9e..928d0fdcda 100644 --- a/app-wear-shared/shared/src/main/java/info/nightscout/shared/extensions/PackageManagerExtension.kt +++ b/app-wear-shared/shared/src/main/java/info/nightscout/shared/extensions/PackageManagerExtension.kt @@ -16,8 +16,12 @@ fun PackageManager.safeGetInstalledPackages(flags: Int): List = * Safe version of queryBroadcastReceivers depending on Android version running */ fun PackageManager.safeQueryBroadcastReceivers(intent: Intent, flags: Int): List = - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) queryBroadcastReceivers(intent, PackageManager.ResolveInfoFlags.of(flags.toLong())) - else @Suppress("DEPRECATION") queryBroadcastReceivers(intent, flags) + try { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) queryBroadcastReceivers(intent, PackageManager.ResolveInfoFlags.of(flags.toLong())) + else @Suppress("DEPRECATION") queryBroadcastReceivers(intent, flags) + } catch (ignored: Exception) { + emptyList() + } /** * Safe version of getPackageInfo depending on Android version running diff --git a/app-wear-shared/shared/src/main/java/info/nightscout/shared/interfaces/ResourceHelper.kt b/app-wear-shared/shared/src/main/java/info/nightscout/shared/interfaces/ResourceHelper.kt index d17cda614e..1235c1d199 100644 --- a/app-wear-shared/shared/src/main/java/info/nightscout/shared/interfaces/ResourceHelper.kt +++ b/app-wear-shared/shared/src/main/java/info/nightscout/shared/interfaces/ResourceHelper.kt @@ -16,8 +16,6 @@ import androidx.annotation.RawRes import androidx.annotation.StringRes interface ResourceHelper { - fun updateContext(ctx: Context?) - fun gs(@StringRes id: Int): String fun gs(@StringRes id: Int, vararg args: Any?): String fun gq(@PluralsRes id: Int, quantity: Int, vararg args: Any?): String diff --git a/app/build.gradle b/app/build.gradle index c1a9ba8c2a..313e32b097 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -111,7 +111,7 @@ android { defaultConfig { multiDexEnabled true versionCode 1500 - version "3.1.0.3-dev-h" + version "3.2.0-dev-j" buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"' buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"' @@ -234,7 +234,7 @@ dependencies { kapt "com.google.dagger:dagger-compiler:$dagger_version" // MainApp - api "com.uber.rxdogtag2:rxdogtag:2.0.1" + api "com.uber.rxdogtag2:rxdogtag:2.0.2" } diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.kt b/app/src/main/java/info/nightscout/androidaps/MainActivity.kt index b42a51e8be..1a7bd16cfe 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.kt @@ -24,6 +24,7 @@ import android.widget.TextView import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.widget.Toolbar import androidx.core.view.GravityCompat +import androidx.core.view.MenuCompat import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.tabs.TabLayoutMediator @@ -302,7 +303,7 @@ class MainActivity : DaggerAppCompatActivityWithResult() { override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) - menu.setGroupDividerEnabled(true) + MenuCompat.setGroupDividerEnabled(menu, true) this.menu = menu menuInflater.inflate(R.menu.menu_main, menu) pluginPreferencesMenuItem = menu.findItem(R.id.nav_plugin_preferences) @@ -477,4 +478,4 @@ class MainActivity : DaggerAppCompatActivityWithResult() { ToastUtils.okToast(context, context.getString(info.nightscout.core.ui.R.string.password_set)) } } -} +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.kt index 6d6759b0e2..83b3813f61 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.kt @@ -11,9 +11,9 @@ import android.widget.TextView import com.google.android.material.datepicker.MaterialDatePicker import com.jjoe64.graphview.GraphView import dagger.android.HasAndroidInjector -import dagger.android.support.DaggerAppCompatActivity import info.nightscout.androidaps.databinding.ActivityHistorybrowseBinding import info.nightscout.core.events.EventIobCalculationProgress +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.core.workflow.CalculationWorkflow import info.nightscout.interfaces.Config @@ -42,7 +42,7 @@ import java.util.GregorianCalendar import javax.inject.Inject import kotlin.math.min -class HistoryBrowseActivity : DaggerAppCompatActivity() { +class HistoryBrowseActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var historyBrowserData: HistoryBrowserData @Inject lateinit var injector: HasAndroidInjector @@ -327,6 +327,7 @@ class HistoryBrowseActivity : DaggerAppCompatActivity() { var useRatioForScale = false var useDSForScale = false var useBGIForScale = false + var useHRForScale = false when { menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true @@ -335,6 +336,7 @@ class HistoryBrowseActivity : DaggerAppCompatActivity() { menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.HR.ordinal] -> useHRForScale = true } val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] @@ -345,6 +347,7 @@ class HistoryBrowseActivity : DaggerAppCompatActivity() { if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8) if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(useRatioForScale, if (useRatioForScale) 1.0 else 0.8) if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && config.isDev()) secondGraphData.addDeviationSlope(useDSForScale, 1.0) + if (menuChartSettings[g + 1][OverviewMenus.CharType.HR.ordinal] && config.isDev()) secondGraphData.addHeartRate(useHRForScale, 1.0) // set manual x bounds to have nice steps secondGraphData.formatAxis(historyBrowserData.overviewData.fromTime, historyBrowserData.overviewData.endTime) diff --git a/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.kt index 7d38e2b6b9..7ea1d46f61 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.kt @@ -1,6 +1,5 @@ package info.nightscout.androidaps.activities -import android.content.Context import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -10,7 +9,6 @@ import androidx.preference.PreferenceScreen import info.nightscout.androidaps.R import info.nightscout.androidaps.databinding.ActivityPreferencesBinding import info.nightscout.configuration.activities.DaggerAppCompatActivityWithResult -import info.nightscout.core.ui.locale.LocaleHelper class PreferencesActivity : DaggerAppCompatActivityWithResult(), PreferenceFragmentCompat.OnPreferenceStartScreenCallback { @@ -66,10 +64,6 @@ class PreferencesActivity : DaggerAppCompatActivityWithResult(), PreferenceFragm return true } - override fun attachBaseContext(newBase: Context) { - super.attachBaseContext(LocaleHelper.wrap(newBase)) - } - override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { android.R.id.home -> { diff --git a/app/src/main/java/info/nightscout/androidaps/implementations/ConfigImpl.kt b/app/src/main/java/info/nightscout/androidaps/implementations/ConfigImpl.kt index 5c5a09f6a2..445b2bfe55 100644 --- a/app/src/main/java/info/nightscout/androidaps/implementations/ConfigImpl.kt +++ b/app/src/main/java/info/nightscout/androidaps/implementations/ConfigImpl.kt @@ -44,6 +44,8 @@ class ConfigImpl @Inject constructor( engineeringMode = engineeringModeSemaphore.exists() && engineeringModeSemaphore.isFile unfinishedMode = unfinishedModeSemaphore.exists() && unfinishedModeSemaphore.isFile devBranch = BuildConfig.VERSION.contains("-") || BuildConfig.VERSION.matches(Regex(".*[a-zA-Z]+.*")) + if (BuildConfig.VERSION.contains("-beta") || BuildConfig.VERSION.contains("-rc")) + devBranch = false } override fun isEngineeringModeOrRelease(): Boolean = diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveWorker.kt b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveWorker.kt index cb64319d68..29e9207399 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveWorker.kt +++ b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveWorker.kt @@ -191,7 +191,7 @@ class KeepAliveWorker( } if (loop.isDisconnected) { // do nothing if pump is disconnected - } else if (runningProfile == null || ((!pump.isThisProfileSet(requestedProfile) || !requestedProfile.isEqual(runningProfile)) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE))) { + } else if (runningProfile == null || ((!pump.isThisProfileSet(requestedProfile) || !requestedProfile.isEqual(runningProfile) || (runningProfile is ProfileSealed.EPS && runningProfile.value.originalEnd < dateUtil.now())) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE))) { rxBus.send(EventProfileSwitchChanged()) } else if (isStatusOutdated && !pump.isBusy()) { lastReadStatus = now diff --git a/app/src/main/res/values-bg-rBG/strings.xml b/app/src/main/res/values-bg-rBG/strings.xml index dfa442d79f..4203ed11d9 100644 --- a/app/src/main/res/values-bg-rBG/strings.xml +++ b/app/src/main/res/values-bg-rBG/strings.xml @@ -21,7 +21,7 @@ Създаване на известия от NS съобщения Когато няма данни повече от [мин] Много стари данни при повече от [мин] - Когато включите Autosense feature трябва да въвеждате ВСИЧКИ въглехидрати. В противен случай те ще се изчисляват грешно като повишена чувствителност!! + Когато включите Autosense, трябва да въвеждате ВСИЧКИ въглехидрати. В противен случай те ще се изчисляват грешно, като повишена чувствителност!! Не всички профили са заредени! Стойностите не са запазени! НЕВАЛИДНО diff --git a/build.gradle b/build.gradle index 98267b201c..9c94775496 100644 --- a/build.gradle +++ b/build.gradle @@ -2,41 +2,40 @@ buildscript { ext { - kotlin_version = '1.8.10' - core_version = '1.9.0' + kotlin_version = '1.8.21' + core_version = '1.10.1' rxjava_version = '3.1.6' rxandroid_version = '3.0.2' rxkotlin_version = '3.0.1' - room_version = '2.5.0' - lifecycle_version = '2.5.1' - dagger_version = '2.45' - coroutines_version = '1.6.4' - activity_version = '1.6.1' - fragmentktx_version = '1.5.5' + room_version = '2.5.1' + lifecycle_version = '2.6.1' + dagger_version = '2.46.1' + coroutines_version = '1.7.1' + activity_version = '1.7.2' + fragmentktx_version = '1.5.7' ormLite_version = '4.46' gson_version = '2.10.1' nav_version = '2.5.3' appcompat_version = '1.6.1' - material_version = '1.8.0' + material_version = '1.9.0' gridlayout_version = '1.0.0' constraintlayout_version = '2.1.4' preferencektx_version = '1.2.0' commonslang3_version = '3.12.0' commonscodec_version = '1.15' jodatime_version = '2.10.14' - work_version = '2.8.0' - tink_version = '1.8.0' + work_version = '2.8.1' + tink_version = '1.9.0' json_version = '20220320' - serialization_version = '1.4.1' - joda_version = '2.12.1.1' + joda_version = '2.12.5' swipe_version = '1.1.0' junit_version = '4.13.2' - junit_jupiter_version = '5.9.2' + junit_jupiter_version = '5.9.3' mockito_version = '4.6.1' dexmaker_version = '1.2' retrofit2_version = '2.9.0' - okhttp3_version = '4.10.0' + okhttp3_version = '4.11.0' byteBuddy_version = '1.12.8' androidx_junit_version = '1.1.4' @@ -50,7 +49,7 @@ buildscript { play_services_location_version = '21.0.1' kotlinx_datetime_version = '0.4.0' - kotlinx_serialization_core_version = '1.4.1' + kotlinx_serialization_version = '1.5.1' } repositories { google() @@ -58,9 +57,9 @@ buildscript { maven { url "https://plugins.gradle.org/m2/" } // jacoco 0.2 } dependencies { - classpath 'com.android.tools.build:gradle:7.4.1' - classpath 'com.google.gms:google-services:4.3.14' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.4' + classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.google.gms:google-services:4.3.15' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.5' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -76,7 +75,7 @@ buildscript { plugins { // Test Gradle build, keep disabled under normal circumstances // id "com.osacky.doctor" version "0.8.1" - id "org.jlleitschuh.gradle.ktlint" version "11.2.0" + id "org.jlleitschuh.gradle.ktlint" version "11.3.2" id 'org.barfuin.gradle.jacocolog' version '3.1.0' id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false } diff --git a/core/graph/src/main/java/info/nightscout/core/graph/OverviewData.kt b/core/graph/src/main/java/info/nightscout/core/graph/OverviewData.kt index 9a1eea13ba..c566cbb511 100644 --- a/core/graph/src/main/java/info/nightscout/core/graph/OverviewData.kt +++ b/core/graph/src/main/java/info/nightscout/core/graph/OverviewData.kt @@ -150,4 +150,7 @@ interface OverviewData { val dsMinScale: Scale var dsMaxSeries: LineGraphSeries var dsMinSeries: LineGraphSeries -} \ No newline at end of file + var heartRateScale: Scale + var heartRateGraphSeries: LineGraphSeries + +} diff --git a/core/graph/src/main/java/info/nightscout/core/graph/data/HeartRateDataPoint.kt b/core/graph/src/main/java/info/nightscout/core/graph/data/HeartRateDataPoint.kt new file mode 100644 index 0000000000..1fef69a2b5 --- /dev/null +++ b/core/graph/src/main/java/info/nightscout/core/graph/data/HeartRateDataPoint.kt @@ -0,0 +1,24 @@ +package info.nightscout.core.graph.data + +import android.content.Context +import android.graphics.Paint +import info.nightscout.database.entities.HeartRate +import info.nightscout.shared.interfaces.ResourceHelper + +class HeartRateDataPoint( + private val data: HeartRate, + private val rh: ResourceHelper, +) : DataPointWithLabelInterface { + + override fun getX(): Double = (data.timestamp - data.duration).toDouble() + override fun getY(): Double = data.beatsPerMinute + override fun setY(y: Double) {} + + override val label: String = "" + override val duration = data.duration + override val shape = PointsWithLabelGraphSeries.Shape.HEARTRATE + override val size = 1f + override val paintStyle: Paint.Style = Paint.Style.FILL + + override fun color(context: Context?): Int = rh.gac(context, info.nightscout.core.ui.R.attr.heartRateColor) +} diff --git a/core/graph/src/main/java/info/nightscout/core/graph/data/PointsWithLabelGraphSeries.java b/core/graph/src/main/java/info/nightscout/core/graph/data/PointsWithLabelGraphSeries.java index 47f744a9fa..49f560efc3 100644 --- a/core/graph/src/main/java/info/nightscout/core/graph/data/PointsWithLabelGraphSeries.java +++ b/core/graph/src/main/java/info/nightscout/core/graph/data/PointsWithLabelGraphSeries.java @@ -54,7 +54,8 @@ public class PointsWithLabelGraphSeries e GENERAL_WITH_DURATION, COB_FAIL_OVER, IOB_PREDICTION, - BUCKETED_BG + BUCKETED_BG, + HEARTRATE, } /** @@ -324,6 +325,10 @@ public class PointsWithLabelGraphSeries e mPaint.setStrokeWidth(5); canvas.drawRect(endX - 3, bounds.top + py - 3, xPlusLength + 3, bounds.bottom + py + 3, mPaint); } + } else if (value.getShape() == Shape.HEARTRATE) { + mPaint.setStyle(Paint.Style.FILL_AND_STROKE); + mPaint.setStrokeWidth(0); + canvas.drawCircle(endX, endY, 1F, mPaint); } // set values above point } diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/logging/UserEntryLogger.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/logging/UserEntryLogger.kt index dd425e5dac..2bd447ca79 100644 --- a/core/interfaces/src/main/java/info/nightscout/interfaces/logging/UserEntryLogger.kt +++ b/core/interfaces/src/main/java/info/nightscout/interfaces/logging/UserEntryLogger.kt @@ -9,6 +9,8 @@ import info.nightscout.interfaces.userEntry.ValueWithUnitMapper interface UserEntryLogger { + fun log(action: Action, source: Sources, note: String?, timestamp: Long, vararg listValues: ValueWithUnit?) + fun log(action: Action, source: Sources, note: String?, timestamp: Long, listValues: List) fun log(action: Action, source: Sources, note: String? = "", vararg listValues: ValueWithUnit?) fun log(action: Action, source: Sources, vararg listValues: ValueWithUnit?) fun log(action: Action, source: Sources, note: String? = "", listValues: List = listOf()) diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/notifications/Notification.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/notifications/Notification.kt index f7bfe91add..066e412c5f 100644 --- a/core/interfaces/src/main/java/info/nightscout/interfaces/notifications/Notification.kt +++ b/core/interfaces/src/main/java/info/nightscout/interfaces/notifications/Notification.kt @@ -133,6 +133,7 @@ open class Notification { const val EOELOW_PATCH_ALERTS = 79 const val COMBO_PUMP_SUSPENDED = 80 const val COMBO_UNKNOWN_TBR = 81 + const val BLUETOOTH_NOT_ENABLED = 82 const val USER_MESSAGE = 1000 diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/nsclient/StoreDataForDb.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/nsclient/StoreDataForDb.kt index 15b548a380..f8bde64a97 100644 --- a/core/interfaces/src/main/java/info/nightscout/interfaces/nsclient/StoreDataForDb.kt +++ b/core/interfaces/src/main/java/info/nightscout/interfaces/nsclient/StoreDataForDb.kt @@ -43,7 +43,12 @@ interface StoreDataForDb { val nsIdDeviceStatuses: MutableList val nsIdFoods: MutableList + val deleteTreatment: MutableList + val deleteGlucoseValue: MutableList + + fun updateDeletedGlucoseValuesInDb() fun storeTreatmentsToDb() + fun updateDeletedTreatmentsInDb() fun storeGlucoseValuesToDb() fun storeFoodsToDb() fun scheduleNsIdUpdate() diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/overview/OverviewMenus.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/overview/OverviewMenus.kt index 0129bcbe19..9622530524 100644 --- a/core/interfaces/src/main/java/info/nightscout/interfaces/overview/OverviewMenus.kt +++ b/core/interfaces/src/main/java/info/nightscout/interfaces/overview/OverviewMenus.kt @@ -15,7 +15,8 @@ interface OverviewMenus { BGI, SEN, ACT, - DEVSLOPE + DEVSLOPE, + HR, } val setting: List> @@ -23,4 +24,4 @@ interface OverviewMenus { fun setupChartMenu(context: Context, chartButton: ImageButton) fun enabledTypes(graph: Int): String fun isEnabledIn(type: CharType): Int -} \ No newline at end of file +} diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/queue/CommandQueue.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/queue/CommandQueue.kt index 50a1900b62..eadf4663db 100644 --- a/core/interfaces/src/main/java/info/nightscout/interfaces/queue/CommandQueue.kt +++ b/core/interfaces/src/main/java/info/nightscout/interfaces/queue/CommandQueue.kt @@ -25,7 +25,6 @@ interface CommandQueue { fun extendedBolus(insulin: Double, durationInMinutes: Int, callback: Callback?): Boolean fun cancelTempBasal(enforceNew: Boolean, callback: Callback?): Boolean fun cancelExtended(callback: Callback?): Boolean - fun setProfile(profile: Profile, hasNsId: Boolean, callback: Callback?): Boolean fun readStatus(reason: String, callback: Callback?): Boolean fun statusInQueue(): Boolean fun loadHistory(type: Byte, callback: Callback?): Boolean diff --git a/core/main/build.gradle b/core/main/build.gradle index 520c97642b..be0a9b8829 100644 --- a/core/main/build.gradle +++ b/core/main/build.gradle @@ -23,7 +23,7 @@ dependencies { api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - api 'com.google.guava:guava:31.1-jre' + api 'com.google.guava:guava:32.0.0-jre' api "androidx.activity:activity-ktx:$activity_version" api "androidx.appcompat:appcompat:$appcompat_version" diff --git a/core/ns-sdk/build.gradle b/core/ns-sdk/build.gradle index 042ea248a1..7678df7245 100644 --- a/core/ns-sdk/build.gradle +++ b/core/ns-sdk/build.gradle @@ -34,5 +34,5 @@ dependencies { api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" api "org.jetbrains.kotlinx:kotlinx-coroutines-rx3:$coroutines_version" - api "org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version" + api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinx_serialization_version" } diff --git a/core/ns-sdk/src/main/java/info/nightscout/sdk/NSAndroidClientImpl.kt b/core/ns-sdk/src/main/java/info/nightscout/sdk/NSAndroidClientImpl.kt index 7f8c934aa2..1f07e76a17 100644 --- a/core/ns-sdk/src/main/java/info/nightscout/sdk/NSAndroidClientImpl.kt +++ b/core/ns-sdk/src/main/java/info/nightscout/sdk/NSAndroidClientImpl.kt @@ -292,14 +292,11 @@ class NSAndroidClientImpl( lastModified = response.body()?.lastModified ) } else throw UnknownResponseNightscoutException() - } else if (response.code() in 400..499) { - return@callWrapper CreateUpdateResponse( - response = response.code(), - identifier = null, - errorResponse = response.errorBody()?.string() ?: response.message() - ) - } else - throw UnsuccessfullNightscoutException(response.errorBody()?.string() ?: response.message()) + } else return@callWrapper CreateUpdateResponse( + response = response.code(), + identifier = null, + errorResponse = response.errorBody()?.string() ?: response.message() + ) } override suspend fun createTreatment(nsTreatment: NSTreatment): CreateUpdateResponse = callWrapper(dispatcher) { diff --git a/core/ns-sdk/src/main/java/info/nightscout/sdk/mapper/TreatmentMapper.kt b/core/ns-sdk/src/main/java/info/nightscout/sdk/mapper/TreatmentMapper.kt index 96b2f8905f..cb3edf420f 100644 --- a/core/ns-sdk/src/main/java/info/nightscout/sdk/mapper/TreatmentMapper.kt +++ b/core/ns-sdk/src/main/java/info/nightscout/sdk/mapper/TreatmentMapper.kt @@ -45,7 +45,7 @@ internal fun RemoteTreatment.toTreatment(): NSTreatment? { subject = this.subject, isReadOnly = this.isReadOnly ?: false, isValid = this.isValid ?: true, - eventType = this.eventType, + eventType = this.eventType ?: EventType.MEAL_BOLUS, notes = this.notes, pumpId = this.pumpId, endId = this.endId, @@ -68,7 +68,7 @@ internal fun RemoteTreatment.toTreatment(): NSTreatment? { subject = this.subject, isReadOnly = this.isReadOnly ?: false, isValid = this.isValid ?: true, - eventType = this.eventType, + eventType = this.eventType ?: EventType.CARBS_CORRECTION, notes = this.notes, pumpId = this.pumpId, endId = this.endId, diff --git a/core/ns-sdk/src/main/java/info/nightscout/sdk/remotemodel/RemoteTreatment.kt b/core/ns-sdk/src/main/java/info/nightscout/sdk/remotemodel/RemoteTreatment.kt index 888110c41f..356a7acb6f 100644 --- a/core/ns-sdk/src/main/java/info/nightscout/sdk/remotemodel/RemoteTreatment.kt +++ b/core/ns-sdk/src/main/java/info/nightscout/sdk/remotemodel/RemoteTreatment.kt @@ -29,7 +29,7 @@ internal data class RemoteTreatment( @SerializedName("modifiedBy") val modifiedBy: String? = null, // string Name of the security subject (within Nightscout scope) which has patched or deleted the document for the last time. This field is automatically set by the server. @SerializedName("isValid") val isValid: Boolean? = null, // boolean A flag set by the server only for deleted documents. This field appears only within history operation and for documents which were deleted by API v3 (and they always have a false value) @SerializedName("isReadOnly") val isReadOnly: Boolean? = null, // boolean A flag set by client that locks the document from any changes. Every document marked with isReadOnly=true is forever immutable and cannot even be deleted. - @SerializedName("eventType") val eventType: EventType, // string "BG Check", "Snack Bolus", "Meal Bolus", "Correction Bolus", "Carb Correction", "Combo Bolus", "Announcement", "Note", "Question", "Exercise", "Site Change", "Sensor Start", "Sensor Change", "Pump Battery Change", "Insulin Change", "Temp Basal", "Profile Switch", "D.A.D. Alert", "Temporary Target", "OpenAPS Offline", "Bolus Wizard" + @SerializedName("eventType") val eventType: EventType?, // string "BG Check", "Snack Bolus", "Meal Bolus", "Correction Bolus", "Carb Correction", "Combo Bolus", "Announcement", "Note", "Question", "Exercise", "Site Change", "Sensor Start", "Sensor Change", "Pump Battery Change", "Insulin Change", "Temp Basal", "Profile Switch", "D.A.D. Alert", "Temporary Target", "OpenAPS Offline", "Bolus Wizard" @SerializedName("glucose") val glucose: Double? = null, // double Current glucose @SerializedName("glucoseType") val glucoseType: String? = null, // string example: "Sensor", "Finger", "Manual" @SerializedName("units") val units: String? = null, // string The units for the glucose value, mg/dl or mmol/l. It is strongly recommended to fill in this field. diff --git a/core/ui/src/main/java/info/nightscout/core/ui/activities/DialogAppCompatActivity.kt b/core/ui/src/main/java/info/nightscout/core/ui/activities/TranslatedDaggerAppCompatActivity.kt similarity index 80% rename from core/ui/src/main/java/info/nightscout/core/ui/activities/DialogAppCompatActivity.kt rename to core/ui/src/main/java/info/nightscout/core/ui/activities/TranslatedDaggerAppCompatActivity.kt index 1d52f7663c..75884c5a6f 100644 --- a/core/ui/src/main/java/info/nightscout/core/ui/activities/DialogAppCompatActivity.kt +++ b/core/ui/src/main/java/info/nightscout/core/ui/activities/TranslatedDaggerAppCompatActivity.kt @@ -4,7 +4,7 @@ import android.content.Context import dagger.android.support.DaggerAppCompatActivity import info.nightscout.core.ui.locale.LocaleHelper -open class DialogAppCompatActivity : DaggerAppCompatActivity() { +open class TranslatedDaggerAppCompatActivity : DaggerAppCompatActivity() { override fun attachBaseContext(newBase: Context) { super.attachBaseContext(LocaleHelper.wrap(newBase)) } diff --git a/core/ui/src/main/java/info/nightscout/core/ui/locale/LocaleHelper.kt b/core/ui/src/main/java/info/nightscout/core/ui/locale/LocaleHelper.kt index 68c94a9d17..a703d9d6d0 100644 --- a/core/ui/src/main/java/info/nightscout/core/ui/locale/LocaleHelper.kt +++ b/core/ui/src/main/java/info/nightscout/core/ui/locale/LocaleHelper.kt @@ -2,19 +2,21 @@ package info.nightscout.core.ui.locale import android.content.Context import android.content.ContextWrapper +import android.content.res.Configuration import android.os.LocaleList import androidx.preference.PreferenceManager import info.nightscout.core.ui.R import java.util.Locale object LocaleHelper { + private fun selectedLanguage(context: Context): String = PreferenceManager.getDefaultSharedPreferences(context).getString(context.getString(R.string.key_language), "default") ?: "default" // injection not possible because of use in attachBaseContext //SP.getString(R.string.key_language, Locale.getDefault().language) - private fun currentLocale(context: Context): Locale { + fun currentLocale(context: Context): Locale { val language = selectedLanguage(context) if (language == "default") return Locale.getDefault() @@ -34,18 +36,13 @@ object LocaleHelper { val locale = currentLocale(context) Locale.setDefault(locale) - val resources = context.resources - val configuration = resources.configuration - context.createConfigurationContext(configuration) - configuration.setLocale(locale) } fun wrap(ctx: Context): Context { // no action for system default language if (selectedLanguage(ctx) == "default") return ctx - val res = ctx.resources - val configuration = res.configuration + val configuration = Configuration() val newLocale = currentLocale(ctx) configuration.setLocale(newLocale) val localeList = LocaleList(newLocale) diff --git a/core/ui/src/main/res/values-bg-rBG/strings.xml b/core/ui/src/main/res/values-bg-rBG/strings.xml index 6bcbc5111b..246d2d368f 100644 --- a/core/ui/src/main/res/values-bg-rBG/strings.xml +++ b/core/ui/src/main/res/values-bg-rBG/strings.xml @@ -164,7 +164,6 @@ Чувств Отмяната на временния базал е неуспешно Неуспешно спиране на удължен болус - Качване на статус в NS или Tidepool Изключен/забранен цикъл Активен Инсулин (IOB) @@ -298,7 +297,6 @@ СМЯНА КАНЮЛА СМЯНА НА РЕЗЕРВОАР КАЛИБРАЦИЯ - БОЛУС ПЪЛНЕНЕНЕ ЛЕЧЕНИЕ ОБНОВИ ПОРТАЛА ЗА ЛЕЧЕНИЯ NS ОБНОВИ СМЯНАТА НА ПРОФИЛ NS @@ -463,10 +461,10 @@ Нарушение на въхлехидратните ограничения! Изчисляване (въглехидратно число: %1$.1f, инсулинова чувствителност: %2$.1f) Въглехидрати: %1$.2fЕ - Остатъчни въглехидрати: %1$.0fg %2$.2fU - Кръвна захар: %1$.2fU + Активни въглехидрати: %1$.0fгр %2$.2fЕ + КЗ: %1$.2fЕ Остатъчен инсулин: %1$.2fU - Суперболус: %1$.2fU + Суперболус: %1$.2fЕ 15\' тенденция: %1$.2fЕ Проценти: %1$.2fЕ x %2$d%% ≈ %3$.2fЕ Нарушение в ограниичението на инсулин!\nНе може да бъде доставен %1$.2fЕ @@ -572,7 +570,7 @@ %1$d минути - Искате ли да почистите базата данни\nТова ще премахне проследените промени и историята на данните, по-стари от 3 месеца. + Искате ли да изтриете базата данни\nТова ще премахне проследените промени и историята на данните, по-стари от 3 месеца. Искате ли да изтриете базата данни?\nТова ще премахне проследените промени и историята на данните, по-стари от 3 месеца.\nИзвършването му ще ускори драстично пълната синхронизация. Изчистени записи diff --git a/core/ui/src/main/res/values-cs-rCZ/strings.xml b/core/ui/src/main/res/values-cs-rCZ/strings.xml index 769a34867e..01c5b42bfb 100644 --- a/core/ui/src/main/res/values-cs-rCZ/strings.xml +++ b/core/ui/src/main/res/values-cs-rCZ/strings.xml @@ -164,7 +164,7 @@ ISF Rušení dočasného bazálu selhalo Zastavení prodlouženého bolusu selhalo - Nahrát stav do NS nebo Tidepoolu + Nahrávat stav pumpy do NS nebo Tidepoolu Zakázaná/pozastavená smyčka Aktivní inzulín (IOB) diff --git a/core/ui/src/main/res/values-de-rDE/strings.xml b/core/ui/src/main/res/values-de-rDE/strings.xml index ca3fe06af5..8d1625810a 100644 --- a/core/ui/src/main/res/values-de-rDE/strings.xml +++ b/core/ui/src/main/res/values-de-rDE/strings.xml @@ -164,7 +164,6 @@ ISF Abbruch der temporären Basalrate fehlgeschlagen Der Abbruch des erweiterten Bolus ist fehlgeschlagen - Status zu NS oder Tidepool hochladen Deaktiviere/Pausiere den Loop Aktives Insulin (IOB) diff --git a/core/ui/src/main/res/values-es-rES/strings.xml b/core/ui/src/main/res/values-es-rES/strings.xml index ac0d470ea1..66ff0c0127 100644 --- a/core/ui/src/main/res/values-es-rES/strings.xml +++ b/core/ui/src/main/res/values-es-rES/strings.xml @@ -164,7 +164,7 @@ ISF Error cancelando la basal temporal Error cancelando el bolo extendido - Subir estado a NS o Tidepool + Subir estado de la bomba a NS o Tidepool Desactiva/suspende el bucle Insulina a bordo (IOB) diff --git a/core/ui/src/main/res/values-fr-rFR/strings.xml b/core/ui/src/main/res/values-fr-rFR/strings.xml index 457b2463ce..d50d8ec938 100644 --- a/core/ui/src/main/res/values-fr-rFR/strings.xml +++ b/core/ui/src/main/res/values-fr-rFR/strings.xml @@ -164,7 +164,7 @@ SI Echec de l\'annulation du basal temporaire Échec de l\'annulation du Bolus étendu - Transférer le statut vers NS ou Tidepool + Télécharger l\'état de la pompe sur NS ou Tidepool Boucle désactivée/suspendue Insuline Active (IA) diff --git a/core/ui/src/main/res/values-it-rIT/strings.xml b/core/ui/src/main/res/values-it-rIT/strings.xml index 3cbcc6ea8a..c43a90f3c3 100644 --- a/core/ui/src/main/res/values-it-rIT/strings.xml +++ b/core/ui/src/main/res/values-it-rIT/strings.xml @@ -164,7 +164,7 @@ ISF Basale temporanea: cancellazione fallita Bolo esteso: cancellazione fallita - Carica stato su NS o Tidepool + Carica stato micro su NS o Tidepool Loop disabilitato/sospeso Insulina attiva (IOB) @@ -449,6 +449,7 @@ INFO Consiglio bolo + Hai una glicemia alta. Invece di mangiare ora, si consiglia di attendere una glicemia migliore. Vuoi fare adesso un bolo di correzione ed essere ricordato quando è il momento di mangiare? In questo caso non verranno registrati carboidrati e dovrai usare di nuovo il calcolatore quando ti verrà mostrato il promemoria. COB vs IOB !!!!! Rilevato assorbimento lento dei carboidrati: %2$d%% del tempo. Ricontrolla il tuo calcolo. COB potrebbero essere sovrastimati e potrebbe essere somministrata più insulina !!!!!]]> Eroga parte del risultato del calcolatore [%] diff --git a/core/ui/src/main/res/values-iw-rIL/strings.xml b/core/ui/src/main/res/values-iw-rIL/strings.xml index c40a47e158..40395a721e 100644 --- a/core/ui/src/main/res/values-iw-rIL/strings.xml +++ b/core/ui/src/main/res/values-iw-rIL/strings.xml @@ -164,7 +164,7 @@ ISF ביטול בזאלי זמני נכשל ביטול בולוס ממושך נכשל - סטטוס העלאה לנייטסקאוט או ל-Tidepool + העלה את סטטוס המשאבה לנייטסקאוט או ל-Tidepool השבתת \\ השהיית לולאה אינסולין פעיל בגוף (IOB) diff --git a/core/ui/src/main/res/values-lt-rLT/strings.xml b/core/ui/src/main/res/values-lt-rLT/strings.xml index 3b80490444..b5aac80d11 100644 --- a/core/ui/src/main/res/values-lt-rLT/strings.xml +++ b/core/ui/src/main/res/values-lt-rLT/strings.xml @@ -23,7 +23,7 @@ mg/dl mmol/l Išsaugoti - Snausti + Nutildyti Virtuali pompa Apribojimai Superbolus @@ -82,7 +82,7 @@ Bluetooth BT Watchdog Vienai sekundei išjungia telefono bluetooth, jei ryšys su pompa nutrūksta. Gali būti veiksminga kai kuriems telefonų modeliams, turintiems BT problemų. - Gerai + OK Pompos laikas pakeistas Išeiti Ištrinti įrašą @@ -164,7 +164,7 @@ JIF Laikinos bazės atšaukti nepavyko Ištęsto boluso atšaukti nepavyko - Įkelti statusą į NS arba Tidepool + Įkelti pompos statusą į NS arba Tidepool Ciklas išjungtas/sustabdytas Aktyvus insulinas organizme (AIO) @@ -232,7 +232,7 @@ Hipo Aktyvumas Išmanieji laikrodžiai - Automatiškai + Automatizacija Pasirinktinis Ciklas NS @@ -288,7 +288,7 @@ ATNAUJINTI SUSTABDYTI HW POMPA LEIDŽIAMA - IŠVALYTI PORAVIMO KODUS + IŠVALYTI SUSIEJIMO KODUS PRIIMTI LAIKINĄ BAZĘ ATŠAUKTI LAIKINĄ BAZĘ ATŠAUKTI BOLUSĄ @@ -387,7 +387,7 @@ LAIKINA BAZĖ %1$d%% %2$d min INSIGHT NUSTATYTI LAIKINĄ BAZĘ PRANEŠIMU STATUSAS %1$s - Tęsti. Būklė per sena. + Tęsti. Statusas senas. Tęsti. Bazė per sena. SMS %1$.0f%% @@ -513,7 +513,7 @@ Pompos prisijungimo klaida Skaitoma pompos istorija Slaptažodis išvalytas! - Sujungiama + Susiejimas Inicijuojama ... Ribojamas maksimalus bazės dydis%1$.2f vv/val dėl %2$s @@ -525,7 +525,7 @@ Patvirtinimas Pranešimas - Gerai + OK Atšaukti ATMESTI Taip @@ -544,13 +544,13 @@ Programai reikalinga Bluetooth prieigos teisė Vartotojo užklausa - Pompa suporuota + Pompa susieta Bluetooth Low Energy nepalaikoma. - Nepalaikomas Bluetooth Low Energy arba įrenginys nesuporuotas. + Nepalaikomas BLE arba įrenginys nesusietas. Bluetooth neįjungta. Vietovės nustatymas neįjungtas - Vietos nustatymo paslauga turi būti įjungta, kad Bluetooth aptikimas veiktų naujesniuose įrenginiuose. AAPS neseka Jūsų lokacijos, o vietos nustatymo paslauga gali būti išjungta po sėkmingo įrenginių suporavimo. + Vietos nustatymo paslauga turi būti įjungta, kad Bluetooth aptikimas veiktų naujesniuose įrenginiuose. AAPS neseka Jūsų lokacijos, o vietos nustatymo paslauga gali būti išjungta po sėkmingo įrenginių susiejimo. Įskiepių nustatymai diff --git a/core/ui/src/main/res/values-night/styles.xml b/core/ui/src/main/res/values-night/styles.xml index 6e3ca1508f..e78363af48 100644 --- a/core/ui/src/main/res/values-night/styles.xml +++ b/core/ui/src/main/res/values-night/styles.xml @@ -216,6 +216,7 @@ @color/inRangeBackground @color/devSlopePos @color/devSlopeNeg + @color/heartRate @color/deviationGrey @color/deviationBlack @color/deviationGreen diff --git a/core/ui/src/main/res/values-nl-rNL/strings.xml b/core/ui/src/main/res/values-nl-rNL/strings.xml index 817d119203..819363ce7b 100644 --- a/core/ui/src/main/res/values-nl-rNL/strings.xml +++ b/core/ui/src/main/res/values-nl-rNL/strings.xml @@ -164,7 +164,6 @@ ISF Annuleren van tijdelijke basaal mislukt Annuleren van vertraagde bolus is mislukt - Upload status naar NS of Tidepool Uitgeschakelde/onderbroken loop Insuline aan boord (IOB) diff --git a/core/ui/src/main/res/values-no-rNO/strings.xml b/core/ui/src/main/res/values-no-rNO/strings.xml index 30a2c96272..fdf2a4eb08 100644 --- a/core/ui/src/main/res/values-no-rNO/strings.xml +++ b/core/ui/src/main/res/values-no-rNO/strings.xml @@ -164,7 +164,7 @@ ISF Kunne ikke avbryte midlertidig basal Kunne ikke avbryte forlenget bolus - Last opp status til NS eller Tidepool + Last opp pumpestatus til NS eller Tidepool Deaktivert/pauset loop Aktivt insulin (IOB) diff --git a/core/ui/src/main/res/values-pl-rPL/strings.xml b/core/ui/src/main/res/values-pl-rPL/strings.xml index 107914c195..cff7211822 100644 --- a/core/ui/src/main/res/values-pl-rPL/strings.xml +++ b/core/ui/src/main/res/values-pl-rPL/strings.xml @@ -164,7 +164,7 @@ ISF Anulowanie bazy tymczasowej nie powiodło się Anulowanie przedłużonego bolusa nie powiodło się - Prześlij status do NS lub Tidepool + Prześlij status pompy do NS lub Tidepool Wyłączona/zawieszona pętla Aktywna insulina (IOB) @@ -532,7 +532,7 @@ Nie Zamknij - Zostaniesz poproszony o hasło główne, które jest potrzebne do odszyfrowania zaimportowanych preferencji. + Zostaniesz poproszony o hasło główne, które jest potrzebne do odszyfrowania zaimportowanych ustawień. zmniejszenie %1$s o %2$s zwiększenie %1$s o %2$s diff --git a/core/ui/src/main/res/values-pt-rBR/strings.xml b/core/ui/src/main/res/values-pt-rBR/strings.xml index 82acc30c09..b1028ee287 100644 --- a/core/ui/src/main/res/values-pt-rBR/strings.xml +++ b/core/ui/src/main/res/values-pt-rBR/strings.xml @@ -164,7 +164,6 @@ FSI Cancelamento do basal temporário falhou Falhou o cancelamento do bolus extendido - Carregar status para NS ou Tidepool Loop Desativado/Suspenso Insulina ativa (IA) diff --git a/core/ui/src/main/res/values-ru-rRU/strings.xml b/core/ui/src/main/res/values-ru-rRU/strings.xml index 3ca057b0aa..dd80e204f1 100644 --- a/core/ui/src/main/res/values-ru-rRU/strings.xml +++ b/core/ui/src/main/res/values-ru-rRU/strings.xml @@ -164,7 +164,7 @@ ISF (чувствительность к инсулину) Отмена врем базала не состоялась Сбой отмены пролонгированного болюса - Статус dsuheprb в NS или Tidepool + Передавать статус помпы в NS или Tidepool Отключенный/приостановленный цикл Активный инсулин (IOB) diff --git a/core/ui/src/main/res/values-sk-rSK/strings.xml b/core/ui/src/main/res/values-sk-rSK/strings.xml index a6bcd9a37d..ea4f1f735d 100644 --- a/core/ui/src/main/res/values-sk-rSK/strings.xml +++ b/core/ui/src/main/res/values-sk-rSK/strings.xml @@ -164,7 +164,7 @@ ISF Zrušenie dočasného bazálu zlyhalo Zastavenie predĺženého bolusu zlyhalo - Nahrať stav do NS, alebo Tidepool + Nahrávať stav pumpy do NS, alebo Tidepoolu Deaktivovaný/pozastavený uzavretý okruh Aktívny inzulín (IOB) diff --git a/core/ui/src/main/res/values-tr-rTR/strings.xml b/core/ui/src/main/res/values-tr-rTR/strings.xml index 7caf16bdbc..97a445df5b 100644 --- a/core/ui/src/main/res/values-tr-rTR/strings.xml +++ b/core/ui/src/main/res/values-tr-rTR/strings.xml @@ -164,7 +164,7 @@ IDF İnsülin Duyarlılık Faktörü Geçici bazal iptali başarısız oldu Yayma bolusun iptal edilmesi başarısız oldu - Durumu NS\'a veya Tidepool\'a yükleyin + Pompa durumunu NS veya Tidepool\'a yükleyin Döngüyü Devre Dışı bırakma/Askıya alma Aktif İnsülin (AİNS) diff --git a/core/ui/src/main/res/values/attrs.xml b/core/ui/src/main/res/values/attrs.xml index 380bb21a83..cc75771f30 100644 --- a/core/ui/src/main/res/values/attrs.xml +++ b/core/ui/src/main/res/values/attrs.xml @@ -181,6 +181,7 @@ + @@ -221,4 +222,4 @@ - \ No newline at end of file + diff --git a/core/ui/src/main/res/values/colors.xml b/core/ui/src/main/res/values/colors.xml index 86f1a39470..91179d4355 100644 --- a/core/ui/src/main/res/values/colors.xml +++ b/core/ui/src/main/res/values/colors.xml @@ -153,6 +153,7 @@ #00EEEE #FFFFFF00 #FFFF00FF + #FFFFFF66 #F6CE22 #FF0000 #7484E2 diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index 66d5e4ed34..badba268be 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 or Tidepool + Upload pump status to NS or Tidepool Disabled/Suspended loop Insulin on Board (IOB) diff --git a/core/ui/src/main/res/values/styles.xml b/core/ui/src/main/res/values/styles.xml index a1bf69ad9d..e9029cc2be 100644 --- a/core/ui/src/main/res/values/styles.xml +++ b/core/ui/src/main/res/values/styles.xml @@ -219,6 +219,7 @@ @color/inRangeBackground @color/devSlopePos @color/devSlopeNeg + @color/heartRate @color/deviationGrey @color/deviationBlack @color/deviationGreen diff --git a/core/utils/build.gradle b/core/utils/build.gradle index 07a9c9d0fc..4d9106b764 100644 --- a/core/utils/build.gradle +++ b/core/utils/build.gradle @@ -22,7 +22,7 @@ dependencies { implementation project(':app-wear-shared:shared') //Firebase - api platform('com.google.firebase:firebase-bom:31.2.2') + api platform('com.google.firebase:firebase-bom:32.1.0') api "com.google.firebase:firebase-analytics-ktx" api "com.google.firebase:firebase-crashlytics-ktx" // StatsActivity not in use now diff --git a/core/utils/src/main/java/info/nightscout/core/utils/ActionModeHelper.kt b/core/utils/src/main/java/info/nightscout/core/utils/ActionModeHelper.kt index 8de965d99e..fc79710d7f 100644 --- a/core/utils/src/main/java/info/nightscout/core/utils/ActionModeHelper.kt +++ b/core/utils/src/main/java/info/nightscout/core/utils/ActionModeHelper.kt @@ -5,6 +5,7 @@ import android.view.ActionMode import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import androidx.core.view.MenuCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import info.nightscout.shared.interfaces.ResourceHelper @@ -68,7 +69,7 @@ class ActionModeHelper(val rh: ResourceHelper, val activity: FragmentActivity } else if (fragment?.isResumed == true) { menu.add(Menu.FIRST, R.id.nav_remove_items, 0, rh.gs(R.string.remove_items)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) menu.add(Menu.FIRST, R.id.nav_sort_items, 0, rh.gs(R.string.sort_items)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) - menu.setGroupDividerEnabled(true) + MenuCompat.setGroupDividerEnabled(menu, true) } } diff --git a/database/entities/build.gradle b/database/entities/build.gradle index d654fc441b..2e4ecc229d 100644 --- a/database/entities/build.gradle +++ b/database/entities/build.gradle @@ -7,6 +7,7 @@ plugins { } apply from: "${project.rootDir}/core/main/android_dependencies.gradle" +apply from: "${project.rootDir}/core/main/test_dependencies.gradle" android { @@ -30,4 +31,4 @@ dependencies { allOpen { // allows mocking for classes w/o directly opening them for release builds annotation 'info.nightscout.database.annotations.DbOpenForTesting' -} \ No newline at end of file +} diff --git a/database/entities/src/main/java/info/nightscout/database/entities/HeartRate.kt b/database/entities/src/main/java/info/nightscout/database/entities/HeartRate.kt new file mode 100644 index 0000000000..edfcb556dc --- /dev/null +++ b/database/entities/src/main/java/info/nightscout/database/entities/HeartRate.kt @@ -0,0 +1,44 @@ +package info.nightscout.database.entities + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import info.nightscout.database.entities.embedments.InterfaceIDs +import info.nightscout.database.entities.interfaces.DBEntryWithTimeAndDuration +import info.nightscout.database.entities.interfaces.TraceableDBEntry +import java.util.* + +/** Heart rate values measured by a user smart watch or the like. */ +@Entity( + tableName = TABLE_HEART_RATE, + indices = [Index("id"), Index("timestamp")] +) +data class HeartRate( + @PrimaryKey(autoGenerate = true) + override var id: Long = 0, + /** Duration milliseconds */ + override var duration: Long, + /** Milliseconds since the epoch. End of the sampling period, i.e. the value is + * sampled from timestamp-duration to timestamp. */ + override var timestamp: Long, + var beatsPerMinute: Double, + /** Source device that measured the heart rate. */ + var device: String, + override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(), + override var version: Int = 0, + override var dateCreated: Long = -1, + override var isValid: Boolean = true, + override var referenceId: Long? = null, + @Embedded + override var interfaceIDs_backing: InterfaceIDs? = null +) : TraceableDBEntry, DBEntryWithTimeAndDuration { + + fun contentEqualsTo(other: HeartRate): Boolean { + return this === other || ( + duration == other.duration && + timestamp == other.timestamp && + beatsPerMinute == other.beatsPerMinute && + isValid == other.isValid) + } +} diff --git a/database/entities/src/main/java/info/nightscout/database/entities/TableNames.kt b/database/entities/src/main/java/info/nightscout/database/entities/TableNames.kt index f174622d05..82635f3b47 100644 --- a/database/entities/src/main/java/info/nightscout/database/entities/TableNames.kt +++ b/database/entities/src/main/java/info/nightscout/database/entities/TableNames.kt @@ -8,6 +8,7 @@ const val TABLE_CARBS = "carbs" const val TABLE_DEVICE_STATUS = "deviceStatus" const val TABLE_EFFECTIVE_PROFILE_SWITCHES = "effectiveProfileSwitches" const val TABLE_EXTENDED_BOLUSES = "extendedBoluses" +const val TABLE_HEART_RATE = "heartRate" const val TABLE_GLUCOSE_VALUES = "glucoseValues" const val TABLE_FOODS = "foods" const val TABLE_MULTIWAVE_BOLUS_LINKS = "multiwaveBolusLinks" diff --git a/database/entities/src/main/java/info/nightscout/database/entities/data/NewEntries.kt b/database/entities/src/main/java/info/nightscout/database/entities/data/NewEntries.kt index b714a18d14..fc70f12a17 100644 --- a/database/entities/src/main/java/info/nightscout/database/entities/data/NewEntries.kt +++ b/database/entities/src/main/java/info/nightscout/database/entities/data/NewEntries.kt @@ -8,6 +8,7 @@ import info.nightscout.database.entities.Carbs import info.nightscout.database.entities.EffectiveProfileSwitch import info.nightscout.database.entities.ExtendedBolus import info.nightscout.database.entities.GlucoseValue +import info.nightscout.database.entities.HeartRate import info.nightscout.database.entities.MultiwaveBolusLink import info.nightscout.database.entities.OfflineEvent import info.nightscout.database.entities.PreferenceChange @@ -35,5 +36,6 @@ data class NewEntries( val temporaryTarget: List, val therapyEvents: List, val totalDailyDoses: List, - val versionChanges: List -) \ No newline at end of file + val versionChanges: List, + val heartRates: List, +) diff --git a/database/entities/src/test/java/info/nightscout/database/entities/HeartRateTest.kt b/database/entities/src/test/java/info/nightscout/database/entities/HeartRateTest.kt new file mode 100644 index 0000000000..e79ec94c5e --- /dev/null +++ b/database/entities/src/test/java/info/nightscout/database/entities/HeartRateTest.kt @@ -0,0 +1,36 @@ +package info.nightscout.database.entities + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.jupiter.api.Test + +class HeartRateTest { + @Test + fun contentEqualsTo_equals() { + val hr1 = createHeartRate() + assertTrue(hr1.contentEqualsTo(hr1)) + assertTrue(hr1.contentEqualsTo(hr1.copy())) + assertTrue(hr1.contentEqualsTo(hr1.copy (id = 2, version = 2, dateCreated = 1L, referenceId = 4L))) + } + + @Test + fun contentEqualsTo_notEquals() { + val hr1 = createHeartRate() + assertFalse(hr1.contentEqualsTo(hr1.copy(duration = 60_001L))) + assertFalse(hr1.contentEqualsTo(hr1.copy(timestamp = 2L))) + assertFalse(hr1.contentEqualsTo(hr1.copy(duration = 60_001L))) + assertFalse(hr1.contentEqualsTo(hr1.copy(beatsPerMinute = 100.0))) + assertFalse(hr1.contentEqualsTo(hr1.copy(isValid = false))) + } + + companion object { + + fun createHeartRate(timestamp: Long? = null, beatsPerMinute: Double = 80.0) = + HeartRate( + timestamp = timestamp ?: System.currentTimeMillis(), + duration = 60_0000L, + beatsPerMinute = beatsPerMinute, + device = "T", + ) + } +} diff --git a/database/impl/build.gradle b/database/impl/build.gradle index 1e7f6251e2..744f89944a 100644 --- a/database/impl/build.gradle +++ b/database/impl/build.gradle @@ -8,6 +8,8 @@ plugins { apply from: "${project.rootDir}/core/main/android_dependencies.gradle" apply from: "${project.rootDir}/core/main/android_module_dependencies.gradle" +apply from: "${project.rootDir}/core/main/test_dependencies.gradle" +apply from: "${project.rootDir}/core/main/jacoco_global.gradle" android { @@ -20,6 +22,9 @@ android { } } } + sourceSets { + androidTest.assets.srcDirs += files("$projectDir/schemas") + } } dependencies { @@ -44,9 +49,11 @@ dependencies { api "com.google.dagger:dagger-android:$dagger_version" api "com.google.dagger:dagger-android-support:$dagger_version" + + androidTestImplementation "androidx.room:room-testing:$room_version" } allOpen { // allows mocking for classes w/o directly opening them for release builds annotation 'info.nightscout.database.annotations.DbOpenForTesting' -} \ No newline at end of file +} diff --git a/database/impl/schemas/info.nightscout.database.impl.AppDatabase/23.json b/database/impl/schemas/info.nightscout.database.impl.AppDatabase/23.json index 41cd0267c9..343f364627 100644 --- a/database/impl/schemas/info.nightscout.database.impl.AppDatabase/23.json +++ b/database/impl/schemas/info.nightscout.database.impl.AppDatabase/23.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 23, - "identityHash": "173734db5f4f35f6295ed953d8124794", + "identityHash": "a3ee37800b6cda170d0ea64799ed7876", "entities": [ { "tableName": "apsResults", @@ -3689,12 +3689,153 @@ ] } ] + }, + { + "tableName": "heartRate", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `duration` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `beatsPerMinute` REAL NOT NULL, `device` TEXT NOT NULL, `utcOffset` INTEGER NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "beatsPerMinute", + "columnName": "beatsPerMinute", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "device", + "columnName": "device", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_heartRate_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_heartRate_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_heartRate_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_heartRate_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [] } ], "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '173734db5f4f35f6295ed953d8124794')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a3ee37800b6cda170d0ea64799ed7876')" ] } } \ No newline at end of file diff --git a/database/impl/schemas/info.nightscout.database.impl.AppDatabase/24.json b/database/impl/schemas/info.nightscout.database.impl.AppDatabase/24.json new file mode 100644 index 0000000000..aebf249065 --- /dev/null +++ b/database/impl/schemas/info.nightscout.database.impl.AppDatabase/24.json @@ -0,0 +1,3841 @@ +{ + "formatVersion": 1, + "database": { + "version": 24, + "identityHash": "a3ee37800b6cda170d0ea64799ed7876", + "entities": [ + { + "tableName": "apsResults", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `algorithm` TEXT NOT NULL, `glucoseStatusJson` TEXT NOT NULL, `currentTempJson` TEXT NOT NULL, `iobDataJson` TEXT NOT NULL, `profileJson` TEXT NOT NULL, `autosensDataJson` TEXT, `mealDataJson` TEXT NOT NULL, `isMicroBolusAllowed` INTEGER, `resultJson` TEXT NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `apsResults`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "algorithm", + "columnName": "algorithm", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "glucoseStatusJson", + "columnName": "glucoseStatusJson", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currentTempJson", + "columnName": "currentTempJson", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iobDataJson", + "columnName": "iobDataJson", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "profileJson", + "columnName": "profileJson", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "autosensDataJson", + "columnName": "autosensDataJson", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mealDataJson", + "columnName": "mealDataJson", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isMicroBolusAllowed", + "columnName": "isMicroBolusAllowed", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "resultJson", + "columnName": "resultJson", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_apsResults_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_apsResults_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_apsResults_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_apsResults_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "apsResults", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "boluses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `amount` REAL NOT NULL, `type` TEXT NOT NULL, `notes` TEXT, `isBasalInsulin` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, `insulinLabel` TEXT, `insulinEndTime` INTEGER, `peak` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `boluses`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notes", + "columnName": "notes", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isBasalInsulin", + "columnName": "isBasalInsulin", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "insulinConfiguration.insulinLabel", + "columnName": "insulinLabel", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "insulinConfiguration.insulinEndTime", + "columnName": "insulinEndTime", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "insulinConfiguration.peak", + "columnName": "peak", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_boluses_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_boluses_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_boluses_temporaryId", + "unique": false, + "columnNames": [ + "temporaryId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_temporaryId` ON `${TABLE_NAME}` (`temporaryId`)" + }, + { + "name": "index_boluses_pumpId", + "unique": false, + "columnNames": [ + "pumpId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_pumpId` ON `${TABLE_NAME}` (`pumpId`)" + }, + { + "name": "index_boluses_pumpSerial", + "unique": false, + "columnNames": [ + "pumpSerial" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_pumpSerial` ON `${TABLE_NAME}` (`pumpSerial`)" + }, + { + "name": "index_boluses_pumpType", + "unique": false, + "columnNames": [ + "pumpType" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_pumpType` ON `${TABLE_NAME}` (`pumpType`)" + }, + { + "name": "index_boluses_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_boluses_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "boluses", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "bolusCalculatorResults", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `targetBGLow` REAL NOT NULL, `targetBGHigh` REAL NOT NULL, `isf` REAL NOT NULL, `ic` REAL NOT NULL, `bolusIOB` REAL NOT NULL, `wasBolusIOBUsed` INTEGER NOT NULL, `basalIOB` REAL NOT NULL, `wasBasalIOBUsed` INTEGER NOT NULL, `glucoseValue` REAL NOT NULL, `wasGlucoseUsed` INTEGER NOT NULL, `glucoseDifference` REAL NOT NULL, `glucoseInsulin` REAL NOT NULL, `glucoseTrend` REAL NOT NULL, `wasTrendUsed` INTEGER NOT NULL, `trendInsulin` REAL NOT NULL, `cob` REAL NOT NULL, `wasCOBUsed` INTEGER NOT NULL, `cobInsulin` REAL NOT NULL, `carbs` REAL NOT NULL, `wereCarbsUsed` INTEGER NOT NULL, `carbsInsulin` REAL NOT NULL, `otherCorrection` REAL NOT NULL, `wasSuperbolusUsed` INTEGER NOT NULL, `superbolusInsulin` REAL NOT NULL, `wasTempTargetUsed` INTEGER NOT NULL, `totalInsulin` REAL NOT NULL, `percentageCorrection` INTEGER NOT NULL, `profileName` TEXT NOT NULL, `note` TEXT NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `bolusCalculatorResults`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "targetBGLow", + "columnName": "targetBGLow", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "targetBGHigh", + "columnName": "targetBGHigh", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "isf", + "columnName": "isf", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "ic", + "columnName": "ic", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "bolusIOB", + "columnName": "bolusIOB", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wasBolusIOBUsed", + "columnName": "wasBolusIOBUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "basalIOB", + "columnName": "basalIOB", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wasBasalIOBUsed", + "columnName": "wasBasalIOBUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "glucoseValue", + "columnName": "glucoseValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wasGlucoseUsed", + "columnName": "wasGlucoseUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "glucoseDifference", + "columnName": "glucoseDifference", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "glucoseInsulin", + "columnName": "glucoseInsulin", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "glucoseTrend", + "columnName": "glucoseTrend", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wasTrendUsed", + "columnName": "wasTrendUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "trendInsulin", + "columnName": "trendInsulin", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "cob", + "columnName": "cob", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wasCOBUsed", + "columnName": "wasCOBUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cobInsulin", + "columnName": "cobInsulin", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "carbs", + "columnName": "carbs", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wereCarbsUsed", + "columnName": "wereCarbsUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "carbsInsulin", + "columnName": "carbsInsulin", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "otherCorrection", + "columnName": "otherCorrection", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wasSuperbolusUsed", + "columnName": "wasSuperbolusUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "superbolusInsulin", + "columnName": "superbolusInsulin", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wasTempTargetUsed", + "columnName": "wasTempTargetUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "totalInsulin", + "columnName": "totalInsulin", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "percentageCorrection", + "columnName": "percentageCorrection", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "profileName", + "columnName": "profileName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_bolusCalculatorResults_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_bolusCalculatorResults_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_bolusCalculatorResults_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_bolusCalculatorResults_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + }, + { + "name": "index_bolusCalculatorResults_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_bolusCalculatorResults_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_bolusCalculatorResults_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_bolusCalculatorResults_isValid` ON `${TABLE_NAME}` (`isValid`)" + } + ], + "foreignKeys": [ + { + "table": "bolusCalculatorResults", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "carbs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `duration` INTEGER NOT NULL, `amount` REAL NOT NULL, `notes` TEXT, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `carbs`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "notes", + "columnName": "notes", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_carbs_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_carbs_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_carbs_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_carbs_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_carbs_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_carbs_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_carbs_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_carbs_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_carbs_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_carbs_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "carbs", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "effectiveProfileSwitches", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `basalBlocks` TEXT NOT NULL, `isfBlocks` TEXT NOT NULL, `icBlocks` TEXT NOT NULL, `targetBlocks` TEXT NOT NULL, `glucoseUnit` TEXT NOT NULL, `originalProfileName` TEXT NOT NULL, `originalCustomizedName` TEXT NOT NULL, `originalTimeshift` INTEGER NOT NULL, `originalPercentage` INTEGER NOT NULL, `originalDuration` INTEGER NOT NULL, `originalEnd` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, `insulinLabel` TEXT NOT NULL, `insulinEndTime` INTEGER NOT NULL, `peak` INTEGER NOT NULL, FOREIGN KEY(`referenceId`) REFERENCES `effectiveProfileSwitches`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "basalBlocks", + "columnName": "basalBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isfBlocks", + "columnName": "isfBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icBlocks", + "columnName": "icBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "targetBlocks", + "columnName": "targetBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "glucoseUnit", + "columnName": "glucoseUnit", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalProfileName", + "columnName": "originalProfileName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalCustomizedName", + "columnName": "originalCustomizedName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalTimeshift", + "columnName": "originalTimeshift", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "originalPercentage", + "columnName": "originalPercentage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "originalDuration", + "columnName": "originalDuration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "originalEnd", + "columnName": "originalEnd", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "insulinConfiguration.insulinLabel", + "columnName": "insulinLabel", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "insulinConfiguration.insulinEndTime", + "columnName": "insulinEndTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "insulinConfiguration.peak", + "columnName": "peak", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_effectiveProfileSwitches_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_effectiveProfileSwitches_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_effectiveProfileSwitches_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_effectiveProfileSwitches_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_effectiveProfileSwitches_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_effectiveProfileSwitches_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + }, + { + "name": "index_effectiveProfileSwitches_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_effectiveProfileSwitches_isValid` ON `${TABLE_NAME}` (`isValid`)" + } + ], + "foreignKeys": [ + { + "table": "effectiveProfileSwitches", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "extendedBoluses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `duration` INTEGER NOT NULL, `amount` REAL NOT NULL, `isEmulatingTempBasal` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `extendedBoluses`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "isEmulatingTempBasal", + "columnName": "isEmulatingTempBasal", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_extendedBoluses_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_extendedBoluses_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_extendedBoluses_endId", + "unique": false, + "columnNames": [ + "endId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_endId` ON `${TABLE_NAME}` (`endId`)" + }, + { + "name": "index_extendedBoluses_pumpSerial", + "unique": false, + "columnNames": [ + "pumpSerial" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_pumpSerial` ON `${TABLE_NAME}` (`pumpSerial`)" + }, + { + "name": "index_extendedBoluses_pumpId", + "unique": false, + "columnNames": [ + "pumpId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_pumpId` ON `${TABLE_NAME}` (`pumpId`)" + }, + { + "name": "index_extendedBoluses_pumpType", + "unique": false, + "columnNames": [ + "pumpType" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_pumpType` ON `${TABLE_NAME}` (`pumpType`)" + }, + { + "name": "index_extendedBoluses_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_extendedBoluses_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "extendedBoluses", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "glucoseValues", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `raw` REAL, `value` REAL NOT NULL, `trendArrow` TEXT NOT NULL, `noise` REAL, `sourceSensor` TEXT NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `glucoseValues`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "raw", + "columnName": "raw", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "trendArrow", + "columnName": "trendArrow", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "noise", + "columnName": "noise", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "sourceSensor", + "columnName": "sourceSensor", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_glucoseValues_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_glucoseValues_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_glucoseValues_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_glucoseValues_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_glucoseValues_sourceSensor", + "unique": false, + "columnNames": [ + "sourceSensor" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_glucoseValues_sourceSensor` ON `${TABLE_NAME}` (`sourceSensor`)" + }, + { + "name": "index_glucoseValues_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_glucoseValues_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_glucoseValues_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_glucoseValues_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "glucoseValues", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "profileSwitches", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `basalBlocks` TEXT NOT NULL, `isfBlocks` TEXT NOT NULL, `icBlocks` TEXT NOT NULL, `targetBlocks` TEXT NOT NULL, `glucoseUnit` TEXT NOT NULL, `profileName` TEXT NOT NULL, `timeshift` INTEGER NOT NULL, `percentage` INTEGER NOT NULL, `duration` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, `insulinLabel` TEXT NOT NULL, `insulinEndTime` INTEGER NOT NULL, `peak` INTEGER NOT NULL, FOREIGN KEY(`referenceId`) REFERENCES `profileSwitches`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "basalBlocks", + "columnName": "basalBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isfBlocks", + "columnName": "isfBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icBlocks", + "columnName": "icBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "targetBlocks", + "columnName": "targetBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "glucoseUnit", + "columnName": "glucoseUnit", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "profileName", + "columnName": "profileName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timeshift", + "columnName": "timeshift", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "percentage", + "columnName": "percentage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "insulinConfiguration.insulinLabel", + "columnName": "insulinLabel", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "insulinConfiguration.insulinEndTime", + "columnName": "insulinEndTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "insulinConfiguration.peak", + "columnName": "peak", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_profileSwitches_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_profileSwitches_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_profileSwitches_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_profileSwitches_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + }, + { + "name": "index_profileSwitches_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_profileSwitches_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_profileSwitches_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_profileSwitches_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_profileSwitches_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_profileSwitches_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + } + ], + "foreignKeys": [ + { + "table": "profileSwitches", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "temporaryBasals", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `type` TEXT NOT NULL, `isAbsolute` INTEGER NOT NULL, `rate` REAL NOT NULL, `duration` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `temporaryBasals`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isAbsolute", + "columnName": "isAbsolute", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "rate", + "columnName": "rate", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_temporaryBasals_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_temporaryBasals_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_temporaryBasals_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_temporaryBasals_pumpType", + "unique": false, + "columnNames": [ + "pumpType" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_pumpType` ON `${TABLE_NAME}` (`pumpType`)" + }, + { + "name": "index_temporaryBasals_endId", + "unique": false, + "columnNames": [ + "endId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_endId` ON `${TABLE_NAME}` (`endId`)" + }, + { + "name": "index_temporaryBasals_pumpSerial", + "unique": false, + "columnNames": [ + "pumpSerial" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_pumpSerial` ON `${TABLE_NAME}` (`pumpSerial`)" + }, + { + "name": "index_temporaryBasals_temporaryId", + "unique": false, + "columnNames": [ + "temporaryId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_temporaryId` ON `${TABLE_NAME}` (`temporaryId`)" + }, + { + "name": "index_temporaryBasals_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_temporaryBasals_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "temporaryBasals", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "temporaryTargets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `reason` TEXT NOT NULL, `highTarget` REAL NOT NULL, `lowTarget` REAL NOT NULL, `duration` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `temporaryTargets`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reason", + "columnName": "reason", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "highTarget", + "columnName": "highTarget", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lowTarget", + "columnName": "lowTarget", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_temporaryTargets_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryTargets_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_temporaryTargets_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryTargets_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_temporaryTargets_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryTargets_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_temporaryTargets_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryTargets_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_temporaryTargets_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryTargets_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "temporaryTargets", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "therapyEvents", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `duration` INTEGER NOT NULL, `type` TEXT NOT NULL, `note` TEXT, `enteredBy` TEXT, `glucose` REAL, `glucoseType` TEXT, `glucoseUnit` TEXT NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `therapyEvents`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "enteredBy", + "columnName": "enteredBy", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "glucose", + "columnName": "glucose", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "glucoseType", + "columnName": "glucoseType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "glucoseUnit", + "columnName": "glucoseUnit", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_therapyEvents_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_therapyEvents_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_therapyEvents_type", + "unique": false, + "columnNames": [ + "type" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_therapyEvents_type` ON `${TABLE_NAME}` (`type`)" + }, + { + "name": "index_therapyEvents_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_therapyEvents_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_therapyEvents_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_therapyEvents_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_therapyEvents_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_therapyEvents_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_therapyEvents_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_therapyEvents_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "therapyEvents", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "totalDailyDoses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `basalAmount` REAL NOT NULL, `bolusAmount` REAL NOT NULL, `totalAmount` REAL NOT NULL, `carbs` REAL NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `totalDailyDoses`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "basalAmount", + "columnName": "basalAmount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "bolusAmount", + "columnName": "bolusAmount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "totalAmount", + "columnName": "totalAmount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "carbs", + "columnName": "carbs", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_totalDailyDoses_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_totalDailyDoses_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_totalDailyDoses_pumpId", + "unique": false, + "columnNames": [ + "pumpId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_totalDailyDoses_pumpId` ON `${TABLE_NAME}` (`pumpId`)" + }, + { + "name": "index_totalDailyDoses_pumpType", + "unique": false, + "columnNames": [ + "pumpType" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_totalDailyDoses_pumpType` ON `${TABLE_NAME}` (`pumpType`)" + }, + { + "name": "index_totalDailyDoses_pumpSerial", + "unique": false, + "columnNames": [ + "pumpSerial" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_totalDailyDoses_pumpSerial` ON `${TABLE_NAME}` (`pumpSerial`)" + }, + { + "name": "index_totalDailyDoses_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_totalDailyDoses_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_totalDailyDoses_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_totalDailyDoses_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_totalDailyDoses_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_totalDailyDoses_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "totalDailyDoses", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "apsResultLinks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `apsResultId` INTEGER NOT NULL, `smbId` INTEGER, `tbrId` INTEGER, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`apsResultId`) REFERENCES `apsResults`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`smbId`) REFERENCES `boluses`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`tbrId`) REFERENCES `temporaryBasals`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`referenceId`) REFERENCES `apsResultLinks`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "apsResultId", + "columnName": "apsResultId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "smbId", + "columnName": "smbId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "tbrId", + "columnName": "tbrId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_apsResultLinks_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_apsResultLinks_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_apsResultLinks_apsResultId", + "unique": false, + "columnNames": [ + "apsResultId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_apsResultLinks_apsResultId` ON `${TABLE_NAME}` (`apsResultId`)" + }, + { + "name": "index_apsResultLinks_smbId", + "unique": false, + "columnNames": [ + "smbId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_apsResultLinks_smbId` ON `${TABLE_NAME}` (`smbId`)" + }, + { + "name": "index_apsResultLinks_tbrId", + "unique": false, + "columnNames": [ + "tbrId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_apsResultLinks_tbrId` ON `${TABLE_NAME}` (`tbrId`)" + } + ], + "foreignKeys": [ + { + "table": "apsResults", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "apsResultId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "boluses", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "smbId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "temporaryBasals", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "tbrId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "apsResultLinks", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "multiwaveBolusLinks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `bolusId` INTEGER NOT NULL, `extendedBolusId` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`bolusId`) REFERENCES `boluses`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`extendedBolusId`) REFERENCES `extendedBoluses`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`referenceId`) REFERENCES `multiwaveBolusLinks`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "bolusId", + "columnName": "bolusId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "extendedBolusId", + "columnName": "extendedBolusId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_multiwaveBolusLinks_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_multiwaveBolusLinks_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_multiwaveBolusLinks_bolusId", + "unique": false, + "columnNames": [ + "bolusId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_multiwaveBolusLinks_bolusId` ON `${TABLE_NAME}` (`bolusId`)" + }, + { + "name": "index_multiwaveBolusLinks_extendedBolusId", + "unique": false, + "columnNames": [ + "extendedBolusId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_multiwaveBolusLinks_extendedBolusId` ON `${TABLE_NAME}` (`extendedBolusId`)" + } + ], + "foreignKeys": [ + { + "table": "boluses", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "bolusId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "extendedBoluses", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "extendedBolusId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "multiwaveBolusLinks", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "preferenceChanges", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `key` TEXT NOT NULL, `value` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "versionChanges", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `versionCode` INTEGER NOT NULL, `versionName` TEXT NOT NULL, `gitRemote` TEXT, `commitHash` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "versionCode", + "columnName": "versionCode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "versionName", + "columnName": "versionName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gitRemote", + "columnName": "gitRemote", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "commitHash", + "columnName": "commitHash", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "userEntry", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `action` TEXT NOT NULL, `source` TEXT NOT NULL, `note` TEXT NOT NULL, `values` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "action", + "columnName": "action", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "values", + "columnName": "values", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_userEntry_source", + "unique": false, + "columnNames": [ + "source" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_userEntry_source` ON `${TABLE_NAME}` (`source`)" + }, + { + "name": "index_userEntry_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_userEntry_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "foods", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `name` TEXT NOT NULL, `category` TEXT, `subCategory` TEXT, `portion` REAL NOT NULL, `carbs` INTEGER NOT NULL, `fat` INTEGER, `protein` INTEGER, `energy` INTEGER, `unit` TEXT NOT NULL, `gi` INTEGER, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `foods`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subCategory", + "columnName": "subCategory", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "portion", + "columnName": "portion", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "carbs", + "columnName": "carbs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fat", + "columnName": "fat", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "protein", + "columnName": "protein", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "energy", + "columnName": "energy", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unit", + "columnName": "unit", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gi", + "columnName": "gi", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_foods_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_foods_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_foods_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_foods_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_foods_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_foods_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_foods_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_foods_isValid` ON `${TABLE_NAME}` (`isValid`)" + } + ], + "foreignKeys": [ + { + "table": "foods", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "deviceStatus", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `device` TEXT, `pump` TEXT, `enacted` TEXT, `suggested` TEXT, `iob` TEXT, `uploaderBattery` INTEGER NOT NULL, `isCharging` INTEGER, `configuration` TEXT, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "device", + "columnName": "device", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "pump", + "columnName": "pump", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "enacted", + "columnName": "enacted", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "suggested", + "columnName": "suggested", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "iob", + "columnName": "iob", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderBattery", + "columnName": "uploaderBattery", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCharging", + "columnName": "isCharging", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "configuration", + "columnName": "configuration", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_deviceStatus_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_deviceStatus_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_deviceStatus_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_deviceStatus_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_deviceStatus_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_deviceStatus_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "offlineEvents", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `reason` TEXT NOT NULL, `duration` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `offlineEvents`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reason", + "columnName": "reason", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_offlineEvents_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_offlineEvents_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_offlineEvents_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_offlineEvents_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_offlineEvents_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_offlineEvents_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_offlineEvents_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_offlineEvents_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_offlineEvents_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_offlineEvents_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "offlineEvents", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "heartRate", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `duration` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `beatsPerMinute` REAL NOT NULL, `device` TEXT NOT NULL, `utcOffset` INTEGER NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "beatsPerMinute", + "columnName": "beatsPerMinute", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "device", + "columnName": "device", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_heartRate_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_heartRate_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_heartRate_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_heartRate_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a3ee37800b6cda170d0ea64799ed7876')" + ] + } +} \ No newline at end of file diff --git a/database/impl/src/androidTest/java/info/nightscout/database/impl/HeartRateDaoTest.kt b/database/impl/src/androidTest/java/info/nightscout/database/impl/HeartRateDaoTest.kt new file mode 100644 index 0000000000..dcd3e6bb6a --- /dev/null +++ b/database/impl/src/androidTest/java/info/nightscout/database/impl/HeartRateDaoTest.kt @@ -0,0 +1,126 @@ +package info.nightscout.database.impl + +import android.content.Context +import androidx.room.Room +import androidx.room.testing.MigrationTestHelper +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import info.nightscout.database.entities.HeartRate +import info.nightscout.database.entities.TABLE_HEART_RATE +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +internal class HeartRateDaoTest { + + private val context = ApplicationProvider.getApplicationContext() + private fun createDatabase() = + Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build() + + private fun getDbObjects(supportDb: SupportSQLiteDatabase, type: String): Set { + val names = mutableSetOf() + supportDb.query("SELECT name FROM sqlite_master WHERE type = '$type'").use { c -> + while (c.moveToNext()) names.add(c.getString(0)) + } + return names + } + + private fun getTableNames(db: SupportSQLiteDatabase) = getDbObjects(db, "table") + private fun getIndexNames(db: SupportSQLiteDatabase) = getDbObjects(db, "index") + + private fun insertAndFind(database: AppDatabase) { + val hr1 = createHeartRate() + val id = database.heartRateDao.insert(hr1) + val hr2 = database.heartRateDao.findById(id) + assertTrue(hr1.contentEqualsTo(hr2!!)) + } + + @Test + fun new_insertAndFind() { + createDatabase().use { db -> insertAndFind(db) } + } + + @Test + fun migrate_createsTableAndIndices() { + val helper = MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), + AppDatabase::class.java + ) + val startVersion = 22 + val supportDb = helper.createDatabase(TEST_DB_NAME, startVersion) + assertFalse(getTableNames(supportDb).contains(TABLE_HEART_RATE)) + DatabaseModule().migrations.filter { m -> m.startVersion >= startVersion }.forEach { m -> m.migrate(supportDb) } + assertTrue(getTableNames(supportDb).contains(TABLE_HEART_RATE)) + assertTrue(getIndexNames(supportDb).contains("index_heartRate_id")) + assertTrue(getIndexNames(supportDb).contains("index_heartRate_timestamp")) + } + + @Test + fun migrate_insertAndFind() { + val helper = MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), + AppDatabase::class.java + ) + // Create the database for version 22 (that's missing the heartRate table). + // helper.createDatabase removes the db file if it already exists. + val supportDb = helper.createDatabase(TEST_DB_NAME, 22) + assertFalse(getTableNames(supportDb).contains(TABLE_HEART_RATE)) + // Room.databaseBuilder will use the previously created db file that has version 22. + Room.databaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java, TEST_DB_NAME) + .addMigrations(*DatabaseModule().migrations) + .build().use { db -> insertAndFind(db) } + } + + @Test + fun getFromTime() { + createDatabase().use { db -> + val dao = db.heartRateDao + val timestamp = System.currentTimeMillis() + val hr1 = createHeartRate(timestamp = timestamp, beatsPerMinute = 80.0) + val hr2 = createHeartRate(timestamp = timestamp + 1, beatsPerMinute = 150.0) + dao.insertNewEntry(hr1) + dao.insertNewEntry(hr2) + + assertEquals(listOf(hr1, hr2), dao.getFromTime(timestamp)) + assertEquals(listOf(hr2), dao.getFromTime(timestamp + 1)) + assertTrue(dao.getFromTime(timestamp + 2).isEmpty()) + } + } + + @Test + fun getFromTimeToTime() { + createDatabase().use { db -> + val dao = db.heartRateDao + val timestamp = System.currentTimeMillis() + val hr1 = createHeartRate(timestamp = timestamp, beatsPerMinute = 80.0) + val hr2 = createHeartRate(timestamp = timestamp + 1, beatsPerMinute = 150.0) + val hr3 = createHeartRate(timestamp = timestamp + 2, beatsPerMinute = 160.0) + dao.insertNewEntry(hr1) + dao.insertNewEntry(hr2) + dao.insertNewEntry(hr3) + + assertEquals(listOf(hr1, hr2, hr3), dao.getFromTimeToTime(timestamp, timestamp + 2)) + assertEquals(listOf(hr1, hr2), dao.getFromTimeToTime(timestamp, timestamp + 1)) + assertEquals(listOf(hr2), dao.getFromTimeToTime(timestamp + 1, timestamp + 1)) + assertTrue(dao.getFromTimeToTime(timestamp + 3, timestamp + 10).isEmpty()) + } + } + + companion object { + private const val TEST_DB_NAME = "testDatabase" + + fun createHeartRate(timestamp: Long? = null, beatsPerMinute: Double = 80.0) = + HeartRate( + timestamp = timestamp ?: System.currentTimeMillis(), + duration = 60_0000L, + beatsPerMinute = beatsPerMinute, + device = "T", + ) + + } +} diff --git a/database/impl/src/androidTest/java/info/nightscout/database/impl/transactions/InsertOrUpdateHeartRateTransactionTest.kt b/database/impl/src/androidTest/java/info/nightscout/database/impl/transactions/InsertOrUpdateHeartRateTransactionTest.kt new file mode 100644 index 0000000000..7f00f1757d --- /dev/null +++ b/database/impl/src/androidTest/java/info/nightscout/database/impl/transactions/InsertOrUpdateHeartRateTransactionTest.kt @@ -0,0 +1,57 @@ +package info.nightscout.database.impl.transactions + +import android.content.Context +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import info.nightscout.database.impl.AppDatabase +import info.nightscout.database.impl.AppRepository +import info.nightscout.database.impl.HeartRateDaoTest +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class InsertOrUpdateHeartRateTransactionTest { + + private val context = ApplicationProvider.getApplicationContext() + private lateinit var db: AppDatabase + private lateinit var repo: AppRepository + + @Before + fun setupUp() { + db = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build() + repo = AppRepository(db) + } + + @After + fun shutdown() { + db.close() + } + + @Test + fun createNewEntry() { + val hr1 = HeartRateDaoTest.createHeartRate() + val result = repo.runTransactionForResult(InsertOrUpdateHeartRateTransaction(hr1)).blockingGet() + assertEquals(listOf(hr1), result.inserted) + assertTrue(result.updated.isEmpty()) + } + + @Test + fun updateEntry() { + val hr1 = HeartRateDaoTest.createHeartRate() + val id = db.heartRateDao.insertNewEntry(hr1) + assertNotEquals(0, id) + val hr2 = hr1.copy(id = id, beatsPerMinute = 181.0) + val result = repo.runTransactionForResult(InsertOrUpdateHeartRateTransaction(hr2)).blockingGet() + assertEquals(listOf(hr2), result.updated) + assertTrue(result.inserted.isEmpty()) + + val hr3 = db.heartRateDao.findById(id)!! + assertTrue(hr2.contentEqualsTo(hr3)) + } +} diff --git a/database/impl/src/main/java/info/nightscout/database/impl/AppDatabase.kt b/database/impl/src/main/java/info/nightscout/database/impl/AppDatabase.kt index 613fa8b634..4cea6766ba 100644 --- a/database/impl/src/main/java/info/nightscout/database/impl/AppDatabase.kt +++ b/database/impl/src/main/java/info/nightscout/database/impl/AppDatabase.kt @@ -33,6 +33,7 @@ import info.nightscout.database.entities.EffectiveProfileSwitch import info.nightscout.database.entities.ExtendedBolus import info.nightscout.database.entities.Food import info.nightscout.database.entities.GlucoseValue +import info.nightscout.database.entities.HeartRate import info.nightscout.database.entities.MultiwaveBolusLink import info.nightscout.database.entities.OfflineEvent import info.nightscout.database.entities.PreferenceChange @@ -43,18 +44,20 @@ import info.nightscout.database.entities.TherapyEvent import info.nightscout.database.entities.TotalDailyDose import info.nightscout.database.entities.UserEntry import info.nightscout.database.entities.VersionChange +import info.nightscout.database.impl.daos.HeartRateDao +import java.io.Closeable -const val DATABASE_VERSION = 23 +const val DATABASE_VERSION = 24 @Database(version = DATABASE_VERSION, entities = [APSResult::class, Bolus::class, BolusCalculatorResult::class, Carbs::class, EffectiveProfileSwitch::class, ExtendedBolus::class, GlucoseValue::class, ProfileSwitch::class, TemporaryBasal::class, TemporaryTarget::class, TherapyEvent::class, TotalDailyDose::class, APSResultLink::class, MultiwaveBolusLink::class, PreferenceChange::class, VersionChange::class, UserEntry::class, - Food::class, DeviceStatus::class, OfflineEvent::class], + Food::class, DeviceStatus::class, OfflineEvent::class, HeartRate::class], exportSchema = true) @TypeConverters(Converters::class) -internal abstract class AppDatabase : RoomDatabase() { +internal abstract class AppDatabase : Closeable, RoomDatabase() { abstract val glucoseValueDao: GlucoseValueDao @@ -96,4 +99,5 @@ internal abstract class AppDatabase : RoomDatabase() { abstract val offlineEventDao: OfflineEventDao -} \ No newline at end of file + abstract val heartRateDao: HeartRateDao +} diff --git a/database/impl/src/main/java/info/nightscout/database/impl/AppRepository.kt b/database/impl/src/main/java/info/nightscout/database/impl/AppRepository.kt index 478aae9239..12e1c629cc 100644 --- a/database/impl/src/main/java/info/nightscout/database/impl/AppRepository.kt +++ b/database/impl/src/main/java/info/nightscout/database/impl/AppRepository.kt @@ -101,6 +101,7 @@ import kotlin.math.roundToInt //database.foodDao.deleteOlderThan(than) removed.add(Pair("DeviceStatus", database.deviceStatusDao.deleteOlderThan(than))) removed.add(Pair("OfflineEvent", database.offlineEventDao.deleteOlderThan(than))) + removed.add(Pair("HeartRate", database.heartRateDao.deleteOlderThan(than))) if (deleteTrackedChanges) { removed.add(Pair("GlucoseValue", database.glucoseValueDao.deleteTrackedChanges())) @@ -119,6 +120,7 @@ import kotlin.math.roundToInt removed.add(Pair("ApsResult", database.apsResultDao.deleteTrackedChanges())) //database.foodDao.deleteHistory() removed.add(Pair("OfflineEvent", database.offlineEventDao.deleteTrackedChanges())) + removed.add(Pair("HeartRate", database.heartRateDao.deleteTrackedChanges())) } val ret = StringBuilder() removed @@ -143,8 +145,8 @@ import kotlin.math.roundToInt .subscribeOn(Schedulers.io()) //BG READINGS -- including invalid/history records - fun findBgReadingByNSIdSingle(nsId: String): Single> = - database.glucoseValueDao.findByNSIdMaybe(nsId).toWrappedSingle() + fun findBgReadingByNSId(nsId: String): GlucoseValue? = + database.glucoseValueDao.findByNSId(nsId) fun getModifiedBgReadingsDataFromId(lastId: Long): Single> = database.glucoseValueDao.getModifiedFrom(lastId) @@ -186,6 +188,9 @@ import kotlin.math.roundToInt .subscribeOn(Schedulers.io()) // TEMP TARGETS + fun findTemporaryTargetByNSId(nsId: String): TemporaryTarget? = + database.temporaryTargetDao.findByNSId(nsId) + /* * returns a Pair of the next entity to sync and the ID of the "update". * The update id might either be the entry id itself if it is a new entry - or the id @@ -253,6 +258,9 @@ import kotlin.math.roundToInt // PROFILE SWITCH + fun findProfileSwitchByNSId(nsId: String): ProfileSwitch? = + database.profileSwitchDao.findByNSId(nsId) + fun getNextSyncElementProfileSwitch(id: Long): Maybe> = database.profileSwitchDao.getNextModifiedOrNewAfter(id) .flatMap { nextIdElement -> @@ -309,6 +317,9 @@ import kotlin.math.roundToInt database.profileSwitchDao.getLastId() // EFFECTIVE PROFILE SWITCH + fun findEffectiveProfileSwitchByNSId(nsId: String): EffectiveProfileSwitch? = + database.effectiveProfileSwitchDao.findByNSId(nsId) + /* * returns a Pair of the next entity to sync and the ID of the "update". * The update id might either be the entry id itself if it is a new entry - or the id @@ -373,6 +384,9 @@ import kotlin.math.roundToInt * * It is a Maybe as there might be no next element. * */ + fun findTherapyEventByNSId(nsId: String): TherapyEvent? = + database.therapyEventDao.findByNSId(nsId) + fun getNextSyncElementTherapyEvent(id: Long): Maybe> = database.therapyEventDao.getNextModifiedOrNewAfter(id) .flatMap { nextIdElement -> @@ -431,6 +445,9 @@ import kotlin.math.roundToInt database.therapyEventDao.getLastId() // FOOD + fun findFoodByNSId(nsId: String): Food? = + database.foodDao.findByNSId(nsId) + /* * returns a Pair of the next entity to sync and the ID of the "update". * The update id might either be the entry id itself if it is a new entry - or the id @@ -465,6 +482,9 @@ import kotlin.math.roundToInt database.foodDao.getLastId() // BOLUS + fun findBolusByNSId(nsId: String): Bolus? = + database.bolusDao.findByNSId(nsId) + /* * returns a Pair of the next entity to sync and the ID of the "update". * The update id might either be the entry id itself if it is a new entry - or the id @@ -531,6 +551,9 @@ import kotlin.math.roundToInt database.bolusDao.getLastId() // CARBS + fun findCarbsByNSId(nsId: String): Carbs? = + database.carbsDao.findByNSId(nsId) + private fun expandCarbs(carbs: Carbs): List = if (carbs.duration == 0L) { listOf(carbs) @@ -646,6 +669,9 @@ import kotlin.math.roundToInt database.carbsDao.getLastId() // BOLUS CALCULATOR RESULT + fun findBolusCalculatorResultByNSId(nsId: String): BolusCalculatorResult? = + database.bolusCalculatorResultDao.findByNSId(nsId) + /* * returns a Pair of the next entity to sync and the ID of the "update". * The update id might either be the entry id itself if it is a new entry - or the id @@ -709,13 +735,16 @@ import kotlin.math.roundToInt database.deviceStatusDao.getLastId() // TEMPORARY BASAL + fun findTemporaryBasalByNSId(nsId: String): TemporaryBasal? = + database.temporaryBasalDao.findByNSId(nsId) + /* - * returns a Pair of the next entity to sync and the ID of the "update". - * The update id might either be the entry id itself if it is a new entry - or the id - * of the update ("historic") entry. The sync counter should be incremented to that id if it was synced successfully. - * - * It is a Maybe as there might be no next element. - * */ + * returns a Pair of the next entity to sync and the ID of the "update". + * The update id might either be the entry id itself if it is a new entry - or the id + * of the update ("historic") entry. The sync counter should be incremented to that id if it was synced successfully. + * + * It is a Maybe as there might be no next element. + * */ fun getNextSyncElementTemporaryBasal(id: Long): Maybe> = database.temporaryBasalDao.getNextModifiedOrNewAfter(id) @@ -773,13 +802,16 @@ import kotlin.math.roundToInt database.temporaryBasalDao.getLastId() // EXTENDED BOLUS + fun findExtendedBolusByNSId(nsId: String): ExtendedBolus? = + database.extendedBolusDao.findByNSId(nsId) + /* - * returns a Pair of the next entity to sync and the ID of the "update". - * The update id might either be the entry id itself if it is a new entry - or the id - * of the update ("historic") entry. The sync counter should be incremented to that id if it was synced successfully. - * - * It is a Maybe as there might be no next element. - * */ + * returns a Pair of the next entity to sync and the ID of the "update". + * The update id might either be the entry id itself if it is a new entry - or the id + * of the update ("historic") entry. The sync counter should be incremented to that id if it was synced successfully. + * + * It is a Maybe as there might be no next element. + * */ fun getNextSyncElementExtendedBolus(id: Long): Maybe> = database.extendedBolusDao.getNextModifiedOrNewAfter(id) @@ -844,6 +876,9 @@ import kotlin.math.roundToInt } // OFFLINE EVENT + fun findOfflineEventByNSId(nsId: String): OfflineEvent? = + database.offlineEventDao.findByNSId(nsId) + /* * returns a Pair of the next entity to sync and the ID of the "update". * The update id might either be the entry id itself if it is a new entry - or the id @@ -897,6 +932,11 @@ import kotlin.math.roundToInt fun getLastOfflineEventId(): Long? = database.offlineEventDao.getLastId() + fun getHeartRatesFromTime(timeMillis: Long) = database.heartRateDao.getFromTime(timeMillis) + + fun getHeartRatesFromTimeToTime(startMillis: Long, endMillis: Long) = + database.heartRateDao.getFromTimeToTime(startMillis, endMillis) + suspend fun collectNewEntriesSince(since: Long, until: Long, limit: Int, offset: Int) = NewEntries( apsResults = database.apsResultDao.getNewEntriesSince(since, until, limit, offset), apsResultLinks = database.apsResultLinkDao.getNewEntriesSince(since, until, limit, offset), @@ -915,6 +955,7 @@ import kotlin.math.roundToInt therapyEvents = database.therapyEventDao.getNewEntriesSince(since, until, limit, offset), totalDailyDoses = database.totalDailyDoseDao.getNewEntriesSince(since, until, limit, offset), versionChanges = database.versionChangeDao.getNewEntriesSince(since, until, limit, offset), + heartRates = database.heartRateDao.getNewEntriesSince(since, until, limit, offset), ) } @@ -923,4 +964,3 @@ inline fun Maybe.toWrappedSingle(): Single> this.map { ValueWrapper.Existing(it) as ValueWrapper } .switchIfEmpty(Maybe.just(ValueWrapper.Absent())) .toSingle() - diff --git a/database/impl/src/main/java/info/nightscout/database/impl/DatabaseModule.kt b/database/impl/src/main/java/info/nightscout/database/impl/DatabaseModule.kt index d6c66dab06..e0d531f2f9 100644 --- a/database/impl/src/main/java/info/nightscout/database/impl/DatabaseModule.kt +++ b/database/impl/src/main/java/info/nightscout/database/impl/DatabaseModule.kt @@ -1,12 +1,14 @@ package info.nightscout.database.impl import android.content.Context +import androidx.annotation.VisibleForTesting import androidx.room.Room import androidx.room.RoomDatabase.Callback import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import dagger.Module import dagger.Provides +import info.nightscout.database.entities.TABLE_HEART_RATE import javax.inject.Qualifier import javax.inject.Singleton @@ -22,13 +24,7 @@ open class DatabaseModule { internal fun provideAppDatabase(context: Context, @DbFileName fileName: String) = Room .databaseBuilder(context, AppDatabase::class.java, fileName) - // .addMigrations(migration5to6) - // .addMigrations(migration6to7) - // .addMigrations(migration7to8) - // .addMigrations(migration11to12) - .addMigrations(migration20to21) - .addMigrations(migration21to22) - .addMigrations(migration22to23) + .addMigrations(*migrations) .addCallback(object : Callback() { override fun onOpen(db: SupportSQLiteDatabase) { super.onOpen(db) @@ -89,4 +85,36 @@ open class DatabaseModule { } } -} \ No newline at end of file + private val migration23to24 = object : Migration(23, 24) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """CREATE TABLE IF NOT EXISTS `$TABLE_HEART_RATE` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `duration` INTEGER NOT NULL, + `timestamp` INTEGER NOT NULL, + `beatsPerMinute` REAL NOT NULL, + `device` TEXT NOT NULL, + `utcOffset` INTEGER NOT NULL, + `version` INTEGER NOT NULL, + `dateCreated` INTEGER NOT NULL, + `isValid` INTEGER NOT NULL, + `referenceId` INTEGER, + `nightscoutSystemId` TEXT, + `nightscoutId` TEXT, + `pumpType` TEXT, + `pumpSerial` TEXT, + `temporaryId` INTEGER, + `pumpId` INTEGER, `startId` INTEGER, + `endId` INTEGER)""".trimIndent() + ) + database.execSQL("""CREATE INDEX IF NOT EXISTS `index_heartRate_id` ON `$TABLE_HEART_RATE` (`id`)""") + database.execSQL("""CREATE INDEX IF NOT EXISTS `index_heartRate_timestamp` ON `$TABLE_HEART_RATE` (`timestamp`)""") + // Custom indexes must be dropped on migration to pass room schema checking after upgrade + dropCustomIndexes(database) + } + } + + /** List of all migrations for easy reply in tests. */ + @VisibleForTesting + internal val migrations = arrayOf(migration20to21, migration21to22, migration22to23, migration23to24) +} diff --git a/database/impl/src/main/java/info/nightscout/database/impl/DelegatedAppDatabase.kt b/database/impl/src/main/java/info/nightscout/database/impl/DelegatedAppDatabase.kt index c8865506af..f14bc62d2d 100644 --- a/database/impl/src/main/java/info/nightscout/database/impl/DelegatedAppDatabase.kt +++ b/database/impl/src/main/java/info/nightscout/database/impl/DelegatedAppDatabase.kt @@ -41,6 +41,8 @@ import info.nightscout.database.impl.daos.delegated.DelegatedTotalDailyDoseDao import info.nightscout.database.impl.daos.delegated.DelegatedUserEntryDao import info.nightscout.database.impl.daos.delegated.DelegatedVersionChangeDao import info.nightscout.database.entities.interfaces.DBEntry +import info.nightscout.database.impl.daos.HeartRateDao +import info.nightscout.database.impl.daos.delegated.DelegatedHeartRateDao internal class DelegatedAppDatabase(val changes: MutableList, val database: AppDatabase) { @@ -64,5 +66,6 @@ internal class DelegatedAppDatabase(val changes: MutableList, val datab val foodDao: FoodDao = DelegatedFoodDao(changes, database.foodDao) val deviceStatusDao: DeviceStatusDao = DelegatedDeviceStatusDao(changes, database.deviceStatusDao) val offlineEventDao: OfflineEventDao = DelegatedOfflineEventDao(changes, database.offlineEventDao) + val heartRateDao: HeartRateDao = DelegatedHeartRateDao(changes, database.heartRateDao) fun clearAllTables() = database.clearAllTables() -} \ No newline at end of file +} diff --git a/database/impl/src/main/java/info/nightscout/database/impl/daos/GlucoseValueDao.kt b/database/impl/src/main/java/info/nightscout/database/impl/daos/GlucoseValueDao.kt index 3b5cb40950..cb9f01b7ca 100644 --- a/database/impl/src/main/java/info/nightscout/database/impl/daos/GlucoseValueDao.kt +++ b/database/impl/src/main/java/info/nightscout/database/impl/daos/GlucoseValueDao.kt @@ -29,7 +29,7 @@ internal interface GlucoseValueDao : TraceableDao { fun getLastId(): Long? @Query("SELECT * FROM $TABLE_GLUCOSE_VALUES WHERE nightscoutId = :nsId AND referenceId IS NULL") - fun findByNSIdMaybe(nsId: String): Maybe + fun findByNSId(nsId: String): GlucoseValue? @Query("SELECT * FROM $TABLE_GLUCOSE_VALUES WHERE timestamp = :timestamp AND sourceSensor = :sourceSensor AND referenceId IS NULL") fun findByTimestampAndSensor(timestamp: Long, sourceSensor: GlucoseValue.SourceSensor): GlucoseValue? diff --git a/database/impl/src/main/java/info/nightscout/database/impl/daos/HeartRateDao.kt b/database/impl/src/main/java/info/nightscout/database/impl/daos/HeartRateDao.kt new file mode 100644 index 0000000000..82ce3f3c38 --- /dev/null +++ b/database/impl/src/main/java/info/nightscout/database/impl/daos/HeartRateDao.kt @@ -0,0 +1,31 @@ +package info.nightscout.database.impl.daos + +import androidx.room.Dao +import androidx.room.Query +import info.nightscout.database.entities.HeartRate +import info.nightscout.database.entities.TABLE_HEART_RATE + +@Dao +internal interface HeartRateDao : TraceableDao { + + @Query("SELECT * FROM $TABLE_HEART_RATE WHERE id = :id") + override fun findById(id: Long): HeartRate? + + @Query("DELETE FROM $TABLE_HEART_RATE") + override fun deleteAllEntries() + + @Query("DELETE FROM $TABLE_HEART_RATE WHERE timestamp < :than") + override fun deleteOlderThan(than: Long): Int + + @Query("DELETE FROM $TABLE_HEART_RATE WHERE referenceId IS NOT NULL") + override fun deleteTrackedChanges(): Int + + @Query("SELECT * FROM $TABLE_HEART_RATE WHERE timestamp >= :timestamp ORDER BY timestamp") + fun getFromTime(timestamp: Long): List + + @Query("SELECT * FROM $TABLE_HEART_RATE WHERE timestamp BETWEEN :startMillis AND :endMillis ORDER BY timestamp") + fun getFromTimeToTime(startMillis: Long, endMillis: Long): List + + @Query("SELECT * FROM $TABLE_HEART_RATE WHERE timestamp > :since AND timestamp <= :until LIMIT :limit OFFSET :offset") + fun getNewEntriesSince(since: Long, until: Long, limit: Int, offset: Int): List +} diff --git a/database/impl/src/main/java/info/nightscout/database/impl/daos/delegated/DelegatedHeartRateDao.kt b/database/impl/src/main/java/info/nightscout/database/impl/daos/delegated/DelegatedHeartRateDao.kt new file mode 100644 index 0000000000..1ea08ad704 --- /dev/null +++ b/database/impl/src/main/java/info/nightscout/database/impl/daos/delegated/DelegatedHeartRateDao.kt @@ -0,0 +1,20 @@ +package info.nightscout.database.impl.daos.delegated + +import info.nightscout.database.entities.HeartRate +import info.nightscout.database.entities.interfaces.DBEntry +import info.nightscout.database.impl.daos.HeartRateDao + +internal class DelegatedHeartRateDao( + changes: MutableList, + private val dao:HeartRateDao): DelegatedDao(changes), HeartRateDao by dao { + + override fun insertNewEntry(entry: HeartRate): Long { + changes.add(entry) + return dao.insertNewEntry(entry) + } + + override fun updateExistingEntry(entry: HeartRate): Long { + changes.add(entry) + return dao.updateExistingEntry(entry) + } +} diff --git a/database/impl/src/main/java/info/nightscout/database/impl/transactions/InsertOrUpdateHeartRateTransaction.kt b/database/impl/src/main/java/info/nightscout/database/impl/transactions/InsertOrUpdateHeartRateTransaction.kt new file mode 100644 index 0000000000..4271c82ba8 --- /dev/null +++ b/database/impl/src/main/java/info/nightscout/database/impl/transactions/InsertOrUpdateHeartRateTransaction.kt @@ -0,0 +1,20 @@ +package info.nightscout.database.impl.transactions + +import info.nightscout.database.entities.HeartRate + +class InsertOrUpdateHeartRateTransaction(private val heartRate: HeartRate): + Transaction() { + + override fun run(): TransactionResult { + val existing = if (heartRate.id == 0L) null else database.heartRateDao.findById(heartRate.id) + return if (existing == null) { + database.heartRateDao.insertNewEntry(heartRate).let { + TransactionResult(listOf(heartRate), emptyList()) } + } else { + database.heartRateDao.updateExistingEntry(heartRate) + TransactionResult(emptyList(), listOf(heartRate)) + } + } + + data class TransactionResult(val inserted: List, val updated: List) +} diff --git a/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateBolusCalculatorResultTransaction.kt b/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateBolusCalculatorResultTransaction.kt index e44afd114e..9b3580e9bb 100644 --- a/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateBolusCalculatorResultTransaction.kt +++ b/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateBolusCalculatorResultTransaction.kt @@ -8,10 +8,11 @@ class InvalidateBolusCalculatorResultTransaction(val id: Long) : Transaction() { + + override fun run(): TransactionResult { + val result = TransactionResult() + val effectiveProfileSwitch = database.effectiveProfileSwitchDao.findById(id) + ?: throw IllegalArgumentException("There is no such EffectiveProfileSwitch with the specified ID.") + if (effectiveProfileSwitch.isValid) { + effectiveProfileSwitch.isValid = false + database.effectiveProfileSwitchDao.updateExistingEntry(effectiveProfileSwitch) + result.invalidated.add(effectiveProfileSwitch) + } + return result + } + + class TransactionResult { + + val invalidated = mutableListOf() + } +} \ No newline at end of file diff --git a/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateExtendedBolusTransaction.kt b/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateExtendedBolusTransaction.kt index b262c53627..e9b4a486fe 100644 --- a/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateExtendedBolusTransaction.kt +++ b/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateExtendedBolusTransaction.kt @@ -8,9 +8,11 @@ class InvalidateExtendedBolusTransaction(val id: Long) : Transaction() { override fun run() { val food = database.foodDao.findById(id) ?: throw IllegalArgumentException("There is no such Food with the specified ID.") - food.isValid = false - database.foodDao.updateExistingEntry(food) + if (food.isValid) { + food.isValid = false + database.foodDao.updateExistingEntry(food) + } } } \ No newline at end of file diff --git a/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateGlucoseValueTransaction.kt b/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateGlucoseValueTransaction.kt index 77a6fd97ae..bfdcda05fd 100644 --- a/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateGlucoseValueTransaction.kt +++ b/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateGlucoseValueTransaction.kt @@ -11,9 +11,11 @@ class InvalidateGlucoseValueTransaction(val id: Long) : Transaction() { - - override fun run() : TransactionResult{ - val result = TransactionResult() - val current = database.profileSwitchDao.findByNSId(nsId) - if (current != null) { - current.isValid = false - database.profileSwitchDao.updateExistingEntry(current) - result.invalidated.add(current) - } - return result - } - - class TransactionResult { - val invalidated = mutableListOf() - } - -} \ No newline at end of file diff --git a/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateOfflineEventTransaction.kt b/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateOfflineEventTransaction.kt index 51cf8fc424..897acf78f3 100644 --- a/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateOfflineEventTransaction.kt +++ b/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateOfflineEventTransaction.kt @@ -1,10 +1,23 @@ package info.nightscout.database.impl.transactions -class InvalidateOfflineEventTransaction(val id: Long) : Transaction() { - override fun run() { +import info.nightscout.database.entities.OfflineEvent + +class InvalidateOfflineEventTransaction(val id: Long) : Transaction() { + + override fun run(): TransactionResult { + val result = TransactionResult() val offlineEvent = database.offlineEventDao.findById(id) ?: throw IllegalArgumentException("There is no such OfflineEvent with the specified ID.") - offlineEvent.isValid = false - database.offlineEventDao.updateExistingEntry(offlineEvent) + if (offlineEvent.isValid) { + offlineEvent.isValid = false + database.offlineEventDao.updateExistingEntry(offlineEvent) + result.invalidated.add(offlineEvent) + } + return result + } + + class TransactionResult { + + val invalidated = mutableListOf() } } \ No newline at end of file diff --git a/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateProfileSwitchTransaction.kt b/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateProfileSwitchTransaction.kt index f5089694fd..7d2bcfcc27 100644 --- a/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateProfileSwitchTransaction.kt +++ b/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateProfileSwitchTransaction.kt @@ -8,9 +8,11 @@ class InvalidateProfileSwitchTransaction(val id: Long) : Transaction() { - override fun run() { +import info.nightscout.database.entities.TemporaryTarget + +class InvalidateTemporaryTargetTransaction(val id: Long) : Transaction() { + + override fun run(): TransactionResult { + val result = TransactionResult() val temporaryTarget = database.temporaryTargetDao.findById(id) ?: throw IllegalArgumentException("There is no such TemporaryTarget with the specified ID.") - temporaryTarget.isValid = false - database.temporaryTargetDao.updateExistingEntry(temporaryTarget) + if (temporaryTarget.isValid) { + temporaryTarget.isValid = false + database.temporaryTargetDao.updateExistingEntry(temporaryTarget) + result.invalidated.add(temporaryTarget) + } + return result + } + + class TransactionResult { + + val invalidated = mutableListOf() } } \ No newline at end of file diff --git a/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateTherapyEventTransaction.kt b/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateTherapyEventTransaction.kt index 2cf2319a3a..b846312bab 100644 --- a/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateTherapyEventTransaction.kt +++ b/database/impl/src/main/java/info/nightscout/database/impl/transactions/InvalidateTherapyEventTransaction.kt @@ -8,9 +8,11 @@ class InvalidateTherapyEventTransaction(val id: Long) : Transaction) { + override fun log(action: Action, source: Sources, note: String?, timestamp: Long, listValues: List) { val filteredValues = listValues.toList().filterNotNull() log( listOf( UserEntry( - timestamp = dateUtil.now(), + timestamp = timestamp, action = action, source = source, note = note ?: "", @@ -48,6 +46,12 @@ class UserEntryLoggerImpl @Inject constructor( ) } + override fun log(action: Action, source: Sources, note: String?, vararg listValues: ValueWithUnit?) = log(action, source, note, listValues.toList()) + + override fun log(action: Action, source: Sources, vararg listValues: ValueWithUnit?) = log(action, source, "", listValues.toList()) + + override fun log(action: Action, source: Sources, note: String?, listValues: List) = log(action, source, note, dateUtil.now(), listValues) + override fun log(entries: List) { compositeDisposable += repository.runTransactionForResult(UserEntryTransaction(entries)) .subscribeOn(aapsSchedulers.io) diff --git a/implementation/src/main/java/info/nightscout/implementation/overview/OverviewDataImpl.kt b/implementation/src/main/java/info/nightscout/implementation/overview/OverviewDataImpl.kt index dd153fa18c..c7bf701bcd 100644 --- a/implementation/src/main/java/info/nightscout/implementation/overview/OverviewDataImpl.kt +++ b/implementation/src/main/java/info/nightscout/implementation/overview/OverviewDataImpl.kt @@ -87,6 +87,7 @@ class OverviewDataImpl @Inject constructor( dsMinSeries = LineGraphSeries() treatmentsSeries = PointsWithLabelGraphSeries() epsSeries = PointsWithLabelGraphSeries() + heartRateGraphSeries = LineGraphSeries() } override fun initRange() { @@ -322,4 +323,6 @@ class OverviewDataImpl @Inject constructor( override val dsMinScale = Scale() override var dsMaxSeries: LineGraphSeries = LineGraphSeries() override var dsMinSeries: LineGraphSeries = LineGraphSeries() + override var heartRateScale = Scale() + override var heartRateGraphSeries: LineGraphSeries = LineGraphSeries() } diff --git a/implementation/src/main/java/info/nightscout/implementation/profile/ProfileFunctionImpl.kt b/implementation/src/main/java/info/nightscout/implementation/profile/ProfileFunctionImpl.kt index 542397cb5d..008fee161b 100644 --- a/implementation/src/main/java/info/nightscout/implementation/profile/ProfileFunctionImpl.kt +++ b/implementation/src/main/java/info/nightscout/implementation/profile/ProfileFunctionImpl.kt @@ -71,7 +71,7 @@ class ProfileFunctionImpl @Inject constructor( override fun getProfileNameWithRemainingTime(): String = getProfileName(System.currentTimeMillis(), customized = true, showRemainingTime = true) - fun getProfileName(time: Long, customized: Boolean, showRemainingTime: Boolean): String { + private fun getProfileName(time: Long, customized: Boolean, showRemainingTime: Boolean): String { var profileName = rh.gs(info.nightscout.core.ui.R.string.no_profile_set) val profileSwitch = repository.getEffectiveProfileSwitchActiveAt(time).blockingGet() diff --git a/implementation/src/main/java/info/nightscout/implementation/pump/PumpSyncImplementation.kt b/implementation/src/main/java/info/nightscout/implementation/pump/PumpSyncImplementation.kt index d7c55ffc8a..4281353859 100644 --- a/implementation/src/main/java/info/nightscout/implementation/pump/PumpSyncImplementation.kt +++ b/implementation/src/main/java/info/nightscout/implementation/pump/PumpSyncImplementation.kt @@ -273,7 +273,13 @@ class PumpSyncImplementation @Inject constructor( pumpSerial = pumpSerial ) ) - uel.log(UserEntry.Action.CAREPORTAL, pumpType.source.toDbSource(), note, ValueWithUnit.Timestamp(timestamp), ValueWithUnit.TherapyEventType(type.toDBbEventType())) + uel.log( + action = UserEntry.Action.CAREPORTAL, + source = pumpType.source.toDbSource(), + note = note, + timestamp = timestamp, + ValueWithUnit.Timestamp(timestamp), ValueWithUnit.TherapyEventType(type.toDBbEventType()) + ) repository.runTransactionForResult(InsertIfNewByTimestampTherapyEventTransaction(therapyEvent)) .doOnError { aapsLogger.error(LTag.DATABASE, "Error while saving TherapyEvent", it) diff --git a/implementation/src/main/java/info/nightscout/implementation/queue/CommandQueueImplementation.kt b/implementation/src/main/java/info/nightscout/implementation/queue/CommandQueueImplementation.kt index 0c2c8ad23d..894d7aa308 100644 --- a/implementation/src/main/java/info/nightscout/implementation/queue/CommandQueueImplementation.kt +++ b/implementation/src/main/java/info/nightscout/implementation/queue/CommandQueueImplementation.kt @@ -113,12 +113,13 @@ class CommandQueueImplementation @Inject constructor( return@subscribe } aapsLogger.debug(LTag.PROFILE, "onEventProfileSwitchChanged") + val effective = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet() profileFunction.getRequestedProfile()?.let { setProfile(ProfileSealed.PS(it), it.interfaceIDs.nightscoutId != null, object : Callback() { override fun run() { if (!result.success) { uiInteraction.runAlarm(result.comment, rh.gs(info.nightscout.core.ui.R.string.failed_update_basal_profile), info.nightscout.core.ui.R.raw.boluserror) - } else if (result.enacted) { + } else if (result.enacted || effective is ValueWrapper.Existing && effective.value.originalEnd < dateUtil.now() && effective.value.originalDuration != 0L) { val nonCustomized = ProfileSealed.PS(it).convertToNonCustomizedProfile(dateUtil) EffectiveProfileSwitch( timestamp = dateUtil.now(), @@ -421,7 +422,7 @@ class CommandQueueImplementation @Inject constructor( } // returns true if command is queued - override fun setProfile(profile: Profile, hasNsId: Boolean, callback: Callback?): Boolean { + fun setProfile(profile: ProfileSealed, hasNsId: Boolean, callback: Callback?): Boolean { if (isRunning(CommandType.BASAL_PROFILE)) { aapsLogger.debug(LTag.PUMPQUEUE, "Command is already executed") callback?.result(PumpEnactResult(injector).success(true).enacted(false))?.run() diff --git a/implementation/src/main/java/info/nightscout/implementation/resources/ResourceHelperImpl.kt b/implementation/src/main/java/info/nightscout/implementation/resources/ResourceHelperImpl.kt index 9e48ac03fb..62d24e8825 100644 --- a/implementation/src/main/java/info/nightscout/implementation/resources/ResourceHelperImpl.kt +++ b/implementation/src/main/java/info/nightscout/implementation/resources/ResourceHelperImpl.kt @@ -19,6 +19,7 @@ import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.view.ContextThemeWrapper import androidx.core.content.ContextCompat import info.nightscout.core.ui.getThemeColor +import info.nightscout.core.ui.locale.LocaleHelper import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.shared.interfaces.ResourceHelper import java.util.Locale @@ -29,15 +30,12 @@ import javax.inject.Inject */ class ResourceHelperImpl @Inject constructor(var context: Context, private val fabricPrivacy: FabricPrivacy) : ResourceHelper { - override fun updateContext(ctx: Context?) { - ctx?.let { context = it } - } - - override fun gs(@StringRes id: Int): String = context.getString(id) + override fun gs(@StringRes id: Int): String = + context.createConfigurationContext(Configuration().apply { setLocale(LocaleHelper.currentLocale(context)) }).resources.getString(id) override fun gs(@StringRes id: Int, vararg args: Any?): String { return try { - context.getString(id, *args) + context.createConfigurationContext(Configuration().apply { setLocale(LocaleHelper.currentLocale(context)) }).resources.getString(id, *args) } catch (exception: Exception) { val resourceName = context.resources.getResourceEntryName(id) val resourceValue = context.getString(id) diff --git a/insight/src/main/res/values-lt-rLT/alert_descriptions.xml b/insight/src/main/res/values-lt-rLT/alert_descriptions.xml index 4c5d7e5b0f..b5272c05dc 100644 --- a/insight/src/main/res/values-lt-rLT/alert_descriptions.xml +++ b/insight/src/main/res/values-lt-rLT/alert_descriptions.xml @@ -3,21 +3,21 @@ %1$d%%\nTrukmė: %2$s h]]> %1$s U]]> Pakeiskite bateriją. - Nustatykite laiką/datą. + Nustatyti laiką/datą. Kreipkitės į Accu-Chek palaikymo tarnybą. %1$d%%
Trukmė: %2$s h]]>
%1$s U
Suleista: %2$s U]]>
Įdėkite rezervuarą. Pakeiskite rezervuarą. Pakeiskite bateriją. - Patikrinkite pompos būseną. + Patikrinkite pompos statusą. Pakeiskite infuzijos rinkinį. Kreipkitės į Accu-Chek palaikymo tarnybą. Pakeiskite rezervuarą. Iš naujo paleiskite duomenų atsisiuntimą. - Patikrinkite pompos būseną. - Nustatykite baterijos tipą. - Nustatykite rezervuaro tipą. + Patikrinkite pompos statusą. + Nustatyti baterijos tipą. + Nustatyti rezervuaro tipą. Pakeiskite bateriją ir rezervuarą. Pakeiskite rezervuarą. Pakeiskite kalbą. diff --git a/insight/src/main/res/values-lt-rLT/strings.xml b/insight/src/main/res/values-lt-rLT/strings.xml index cbff9dfcae..0c3757d27a 100644 --- a/insight/src/main/res/values-lt-rLT/strings.xml +++ b/insight/src/main/res/values-lt-rLT/strings.xml @@ -4,7 +4,7 @@ Išjungti pranešimus apie LBD pabaigą\n(pompos nustatymai) Nesusieta Atstatoma - Būsena + Statusas Atkūrimo trukmė Paskutinis prisijungimas Paleisti pompą @@ -21,9 +21,9 @@ %1$.2f / %2$.2f vv per %3$d min Paskutinis bolusas Ieškoma įrenginių… - Sujungimas sėkmingas + Sėkmingai susieta Ar kodas, kurį matote įrenginyje, sutampa su pompos kodu? - Insight sujungimas + Insight susiejimas Accu-Chek Insight %1$s: %2$s Kateteris pakeistas @@ -36,7 +36,7 @@ Ryšio užmezgimui skirtas laikas baigėsi - iš naujo nustatykite bluetooth Pompa sustabdyta Pompa paleista - Paskutinis prisijungimas: prieš %1$d min + Pask. prijung: prieš %1$d min LBD: %1$d%% - %2$d / %3$d min Ištęstas: %1$.2f / %2$.2f V %3$d min Daugiabangis: %1$.2f / %2$.2f vv %3$d min @@ -52,7 +52,7 @@ Bluetooth adresas Sistemos ID priedėlis Pagaminimo data - Panaikinti sąsają + Panaikinti susiejimą Įrašyti adatos pakeitimą Įrašyti rezervuaro keitimus Įrašyti infuzijos vamzdelio pakeitimą @@ -65,6 +65,6 @@ Didž. atkūrimo trukmė [s] Min. atkūrimo trukmė [s] Pompos aliarmas - Sąsajos informacija - Insight Mygtukas Naujinti + Susiejimo informacija + Naujinti Insight mygtukas diff --git a/plugins/aps/src/main/java/info/nightscout/plugins/aps/OpenAPSFragment.kt b/plugins/aps/src/main/java/info/nightscout/plugins/aps/OpenAPSFragment.kt index 5bc4c7d367..293e048e61 100644 --- a/plugins/aps/src/main/java/info/nightscout/plugins/aps/OpenAPSFragment.kt +++ b/plugins/aps/src/main/java/info/nightscout/plugins/aps/OpenAPSFragment.kt @@ -10,6 +10,7 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.core.view.MenuCompat import androidx.core.view.MenuProvider import androidx.lifecycle.Lifecycle import dagger.android.support.DaggerFragment @@ -72,7 +73,7 @@ class OpenAPSFragment : DaggerFragment(), MenuProvider { override fun onCreateMenu(menu: Menu, inflater: MenuInflater) { menu.add(Menu.FIRST, ID_MENU_RUN, 0, rh.gs(R.string.openapsma_run)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) - menu.setGroupDividerEnabled(true) + MenuCompat.setGroupDividerEnabled(menu, true) } override fun onMenuItemSelected(item: MenuItem): Boolean = diff --git a/plugins/aps/src/main/java/info/nightscout/plugins/aps/loop/LoopFragment.kt b/plugins/aps/src/main/java/info/nightscout/plugins/aps/loop/LoopFragment.kt index d49c72cab4..e6ca338044 100644 --- a/plugins/aps/src/main/java/info/nightscout/plugins/aps/loop/LoopFragment.kt +++ b/plugins/aps/src/main/java/info/nightscout/plugins/aps/loop/LoopFragment.kt @@ -9,6 +9,7 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.core.view.MenuCompat import androidx.core.view.MenuProvider import androidx.lifecycle.Lifecycle import dagger.android.support.DaggerFragment @@ -74,7 +75,7 @@ class LoopFragment : DaggerFragment(), MenuProvider { override fun onCreateMenu(menu: Menu, inflater: MenuInflater) { menu.add(Menu.FIRST, ID_MENU_RUN, 0, rh.gs(R.string.run_now)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) - menu.setGroupDividerEnabled(true) + MenuCompat.setGroupDividerEnabled(menu, true) } override fun onMenuItemSelected(item: MenuItem): Boolean = @@ -169,4 +170,4 @@ class LoopFragment : DaggerFragment(), MenuProvider { binding.smbsetbypump.text = "" binding.swipeRefresh.isRefreshing = false } -} +} \ No newline at end of file diff --git a/plugins/aps/src/main/res/values-lt-rLT/strings.xml b/plugins/aps/src/main/res/values-lt-rLT/strings.xml index 28da51223f..34610ef3d5 100644 --- a/plugins/aps/src/main/res/values-lt-rLT/strings.xml +++ b/plugins/aps/src/main/res/values-lt-rLT/strings.xml @@ -25,7 +25,7 @@ 2017 m. algoritmas Naujausias algoritmas patyrusiems vartotojams Naujausias algoritmas patyrusiems naudotojams su dinaminiu/automatiniu JIF - Boluso snaudimo daliklis + Atidėjimo po boluso IVT daliklis Paleisti dabar Paskutinis veiksmas Įvesties parametrai @@ -48,7 +48,7 @@ Numatytoji reikšmė: 3.0 (AMA) arba 8.0 (SMB). Tai parametras, nurodantis angliavandenių poveikį glikemijai kas 5 minutes nuo jų suvartojimo. Numatytoji reikšmė yra 3 mg/dl per 5min. Šis skaičius turi įtakos apskaičiavimams, kaip greitai mažės AAO, kokia bus glikemijos kitimo prognozė, ypač kai ji krenta daugiau nei tikėtasi, arba nedidėja tiek, kiek tikėtasi. Numatytoji reikšmė: 3 tai pagrindinis OpenAPS saugiklis. Jis apriboja Jūsų valandinę bazę iki trigubos maksimalios valandinės bazės (standartiniu atveju). Jums greičiausiai neprireiks šios reikšmės keisti, tačiau turėtumėte žinoti, kad ji naudojama kaip saugiklis apskaičiuojant \"3x maksimali dienos bazė; 4x dabartinė valandinė bazė\". Numatytoji reikšmė: 4 tai antras pagrindinis OpenAPS saugiklis, apskaičiuojant \"3x maksimali dienos bazė; 4x dabartinė valandinė bazė\". Jis reiškia, kad jūsų valandinė bazė, nepriklausomai nuo to, kokia maksimali valandinė bazė suprogramuota pompoje, negali būti didesnė, nei keturguba dabartinė valandinė bazė. Tai apsaugo Jus nuo pavojingų situacijų, kai nustatoma pernelyg didelė valandinė bazė, pilnai nesuprantant, kaip veikia algoritmas. Numatytoji reikšmė yra 4x; daugumai vartotojų niekada neprireikia šio skaičiaus keisti, o pajutus, kad \"atsitrenkiama\" į saugiklį, rekomenduojama peržiūrėti kitus nustatymus. - Numatytoji reikšmė: 2\nBoluso snaudimas aktyvuojamas iškart po to, kai susileidžiate bolusą maistui. Ši funkcija neleidžia sistemai nustatyti mažų LBD iškart po valgio. Pvz.: jei IVT yra 3 val, tai boluso snaudimas pamažu deaktyvuojamas per 1,5 val (3 val. / 2). + Numatytoji reikšmė: 2\nAtidėjimas po boluso aktyvuojamas iškart po to, kai susileidžiate bolusą maistui. Ši funkcija neleidžia sistemai nustatyti mažų laikinų bazių iškart po valgio. Pvz.: jei IVT yra 3 val, tai atidėjimas po boluso pamažu deaktyvuojamas per 1,5 val (3 val. / 2). Dėmesio!\nPaprastai neturėtumėte keisti šių, žemiau esančių, reikšmių. Prašome PASPAUSTI ČIA ir PERSKAITYKITE tekstą ir įsitikinkite, kad SUPRANTATE prieš keisdami bet kurią iš šių verčių. Visada naudoti trumpo laikotarpio vidutinį pokyti vietoj paprasto pokyčio Naudinga, kai duomenys, gaunami iš nefiltruoto šaltinio, tokio kaip xDrip+, tampa nestabilūs. diff --git a/plugins/automation/src/main/java/info/nightscout/automation/AutomationFragment.kt b/plugins/automation/src/main/java/info/nightscout/automation/AutomationFragment.kt index ac0e7c63cb..610ae67a12 100644 --- a/plugins/automation/src/main/java/info/nightscout/automation/AutomationFragment.kt +++ b/plugins/automation/src/main/java/info/nightscout/automation/AutomationFragment.kt @@ -16,6 +16,7 @@ import android.widget.ImageView import android.widget.LinearLayout import androidx.annotation.DrawableRes import androidx.core.util.forEach +import androidx.core.view.MenuCompat import androidx.core.view.MenuProvider import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.ItemTouchHelper @@ -61,7 +62,6 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener, MenuProvider { const val ID_MENU_ADD = 504 const val ID_MENU_RUN = 505 - const val ID_MENU_EDIT_MOVE = 506 } private var disposable: CompositeDisposable = CompositeDisposable() @@ -98,8 +98,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener, MenuProvider { actionHelper.onCreateOptionsMenu(menu, inflater) menu.add(Menu.FIRST, ID_MENU_ADD, 0, rh.gs(R.string.add_automation)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) menu.add(Menu.FIRST, ID_MENU_RUN, 0, rh.gs(R.string.run_automations)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) - menu.add(Menu.FIRST, ID_MENU_EDIT_MOVE, 0, rh.gs(R.string.remove_sort)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) - menu.setGroupDividerEnabled(true) + MenuCompat.setGroupDividerEnabled(menu, true) } override fun onMenuItemSelected(item: MenuItem): Boolean = @@ -115,11 +114,6 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener, MenuProvider { true } - ID_MENU_EDIT_MOVE -> { - actionHelper.startAction() - true - } - else -> super.onContextItemSelected(item) } @@ -316,4 +310,4 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener, MenuProvider { } }.show(childFragmentManager, "EditEventDialog") } -} +} \ No newline at end of file diff --git a/plugins/automation/src/main/res/values-bg-rBG/strings.xml b/plugins/automation/src/main/res/values-bg-rBG/strings.xml index 92d6c27b1e..0aae953888 100644 --- a/plugins/automation/src/main/res/values-bg-rBG/strings.xml +++ b/plugins/automation/src/main/res/values-bg-rBG/strings.xml @@ -116,7 +116,6 @@ Системна автомация Стартирай автомаций Добавяне на правило - Премахни/сортирай Спри изпълнението П diff --git a/plugins/automation/src/main/res/values-cs-rCZ/strings.xml b/plugins/automation/src/main/res/values-cs-rCZ/strings.xml index ecc527e627..a0430201bf 100644 --- a/plugins/automation/src/main/res/values-cs-rCZ/strings.xml +++ b/plugins/automation/src/main/res/values-cs-rCZ/strings.xml @@ -116,7 +116,6 @@ Automatizace systému Spustit automatizace Přidat pravidlo - Odstranit/řadit Zastavit zpracovávání Po diff --git a/plugins/automation/src/main/res/values-da-rDK/strings.xml b/plugins/automation/src/main/res/values-da-rDK/strings.xml index 8dd3478e8c..7a52bf40a9 100644 --- a/plugins/automation/src/main/res/values-da-rDK/strings.xml +++ b/plugins/automation/src/main/res/values-da-rDK/strings.xml @@ -116,7 +116,6 @@ System automatisering Kør automatisering Tilføj regel - Fjern/sorter Stop afvikling Ma diff --git a/plugins/automation/src/main/res/values-de-rDE/strings.xml b/plugins/automation/src/main/res/values-de-rDE/strings.xml index 31312650d1..62e7b54804 100644 --- a/plugins/automation/src/main/res/values-de-rDE/strings.xml +++ b/plugins/automation/src/main/res/values-de-rDE/strings.xml @@ -116,7 +116,6 @@ System-Automatisierung Automatisierungen ausführen Regel hinzufügen - Entfernen/sortieren Verarbeitung beenden Mo diff --git a/plugins/automation/src/main/res/values-es-rES/strings.xml b/plugins/automation/src/main/res/values-es-rES/strings.xml index ba980e77cf..c5bfc38eba 100644 --- a/plugins/automation/src/main/res/values-es-rES/strings.xml +++ b/plugins/automation/src/main/res/values-es-rES/strings.xml @@ -116,7 +116,6 @@ Automatización del sistema Ejecutar automatizaciones Añadir regla - Eliminar/ordenar Parar procesamiento M diff --git a/plugins/automation/src/main/res/values-fr-rFR/strings.xml b/plugins/automation/src/main/res/values-fr-rFR/strings.xml index 66758a46f2..70c5466d0a 100644 --- a/plugins/automation/src/main/res/values-fr-rFR/strings.xml +++ b/plugins/automation/src/main/res/values-fr-rFR/strings.xml @@ -116,7 +116,6 @@ Système d\'automatisation Exécuter les automatisations Ajouter une règle - Supprimer/trier Arrêter le traitement L diff --git a/plugins/automation/src/main/res/values-it-rIT/strings.xml b/plugins/automation/src/main/res/values-it-rIT/strings.xml index 78695a69e6..0ac3c06831 100644 --- a/plugins/automation/src/main/res/values-it-rIT/strings.xml +++ b/plugins/automation/src/main/res/values-it-rIT/strings.xml @@ -116,7 +116,6 @@ Automazione sistema Esegui automazioni Aggiungi regola - Rimuovi/ordina Interrompere l\'elaborazione L diff --git a/plugins/automation/src/main/res/values-iw-rIL/strings.xml b/plugins/automation/src/main/res/values-iw-rIL/strings.xml index d376bc6321..58c5477995 100644 --- a/plugins/automation/src/main/res/values-iw-rIL/strings.xml +++ b/plugins/automation/src/main/res/values-iw-rIL/strings.xml @@ -116,7 +116,6 @@ אוטומציית מערכת הפעל אוטומציות הוספת כלל - הסרה\\סידור עצור עיבוד ב\' diff --git a/plugins/automation/src/main/res/values-lt-rLT/strings.xml b/plugins/automation/src/main/res/values-lt-rLT/strings.xml index 6d3d5739eb..0723c57a91 100644 --- a/plugins/automation/src/main/res/values-lt-rLT/strings.xml +++ b/plugins/automation/src/main/res/values-lt-rLT/strings.xml @@ -27,13 +27,13 @@ Siųsti SMS: %1$s Siųsti SMS į visus numerius Išsiųsti SMS žinutę su tekstu - Nustatykite laikiną tikslą - mažesnis nei - lygus arba mažesnis nei - yra lygus - yra lygus arba didesnis nei - didesnis nei - negalimas + Pradėti laikiną tikslą + mažiau nei + lygu arba mažiau nei + lygu + lygu arba daugiau nei + daugiau nei + nėra duomenų Glikemija nepasiekiama Glikemija %1$s %2$.0f %3$s Glikemija %1$s %2$.1f %3$s @@ -116,7 +116,6 @@ Sistemos automatizacija Paleisti automatizacijas Pridėti taisyklę - Pašalinti/rūšiuoti Stabdyti apdorojimą P diff --git a/plugins/automation/src/main/res/values-nl-rNL/strings.xml b/plugins/automation/src/main/res/values-nl-rNL/strings.xml index e7b7905ac1..be72130e16 100644 --- a/plugins/automation/src/main/res/values-nl-rNL/strings.xml +++ b/plugins/automation/src/main/res/values-nl-rNL/strings.xml @@ -116,7 +116,6 @@ Systeem automatisering Automatisering uitvoeren Regel toevoegen - Verwijderen/sorteren Stop met verwerken M diff --git a/plugins/automation/src/main/res/values-no-rNO/strings.xml b/plugins/automation/src/main/res/values-no-rNO/strings.xml index 36e6abba2f..a7001a7448 100644 --- a/plugins/automation/src/main/res/values-no-rNO/strings.xml +++ b/plugins/automation/src/main/res/values-no-rNO/strings.xml @@ -116,7 +116,6 @@ Systemautomatisering Kjør automatiseringer Legg til regel - Fjern/sorter Stopp prosessering M diff --git a/plugins/automation/src/main/res/values-pl-rPL/strings.xml b/plugins/automation/src/main/res/values-pl-rPL/strings.xml index 13cb63aa8a..281b2ae3a9 100644 --- a/plugins/automation/src/main/res/values-pl-rPL/strings.xml +++ b/plugins/automation/src/main/res/values-pl-rPL/strings.xml @@ -116,7 +116,6 @@ Automatyzacja systemowa Uruchom automatyzację Dodaj regułę - Usuń / sortuj Zatrzymaj przetwarzanie P diff --git a/plugins/automation/src/main/res/values-pt-rBR/strings.xml b/plugins/automation/src/main/res/values-pt-rBR/strings.xml index ea1a750c8d..71b911bdd2 100644 --- a/plugins/automation/src/main/res/values-pt-rBR/strings.xml +++ b/plugins/automation/src/main/res/values-pt-rBR/strings.xml @@ -116,7 +116,6 @@ Automação do Sistema Executar automações Adicionar regra - Remover/ordenar Parar processamento 2a diff --git a/plugins/automation/src/main/res/values-ru-rRU/strings.xml b/plugins/automation/src/main/res/values-ru-rRU/strings.xml index bd54917919..e27718b842 100644 --- a/plugins/automation/src/main/res/values-ru-rRU/strings.xml +++ b/plugins/automation/src/main/res/values-ru-rRU/strings.xml @@ -116,7 +116,6 @@ Автоматизация системы Запустить автоматизацию Добавить правило - Удаление/сортировка Остановить обработку Пн diff --git a/plugins/automation/src/main/res/values-sk-rSK/strings.xml b/plugins/automation/src/main/res/values-sk-rSK/strings.xml index e85e2a2884..6b1f62b829 100644 --- a/plugins/automation/src/main/res/values-sk-rSK/strings.xml +++ b/plugins/automation/src/main/res/values-sk-rSK/strings.xml @@ -116,7 +116,6 @@ Automatizácia systému Spustiť automatizácie Pridať pravidlo - Odstrániť/zoradiť Zastaviť spracovanie Po diff --git a/plugins/automation/src/main/res/values-tr-rTR/strings.xml b/plugins/automation/src/main/res/values-tr-rTR/strings.xml index 7ea88e4681..8bba1d3ee0 100644 --- a/plugins/automation/src/main/res/values-tr-rTR/strings.xml +++ b/plugins/automation/src/main/res/values-tr-rTR/strings.xml @@ -116,7 +116,6 @@ Sistem otomasyonu Otomasyonları çalıştır Kural ekle - Kaldır/sırala İşlemeyi durdur Pzt diff --git a/plugins/automation/src/main/res/values-zh-rCN/strings.xml b/plugins/automation/src/main/res/values-zh-rCN/strings.xml index 80659e0799..cbc0489707 100644 --- a/plugins/automation/src/main/res/values-zh-rCN/strings.xml +++ b/plugins/automation/src/main/res/values-zh-rCN/strings.xml @@ -116,7 +116,6 @@ 系统自动化 运行自动化 添加规则 - 移除/排序 周一 周二 diff --git a/plugins/automation/src/main/res/values/strings.xml b/plugins/automation/src/main/res/values/strings.xml index fb351e0ccc..aff3deb360 100644 --- a/plugins/automation/src/main/res/values/strings.xml +++ b/plugins/automation/src/main/res/values/strings.xml @@ -119,7 +119,6 @@ System automation Run automations Add rule - Remove/sort Stop processing diff --git a/plugins/automation/src/test/java/info/nightscout/automation/triggers/TriggerWifiSsidTest.kt b/plugins/automation/src/test/java/info/nightscout/automation/triggers/TriggerWifiSsidTest.kt index 7afe017f18..d1097d1ef1 100644 --- a/plugins/automation/src/test/java/info/nightscout/automation/triggers/TriggerWifiSsidTest.kt +++ b/plugins/automation/src/test/java/info/nightscout/automation/triggers/TriggerWifiSsidTest.kt @@ -22,16 +22,16 @@ class TriggerWifiSsidTest : TriggerTestBase() { @Test fun shouldRunTest() { val e = EventNetworkChange() `when`(receiverStatusStore.lastNetworkEvent).thenReturn(e) - var t: TriggerWifiSsid = TriggerWifiSsid(injector).setValue("aSSID").comparator(Comparator.Compare.IS_EQUAL) + var t: TriggerWifiSsid = TriggerWifiSsid(injector).setValue("aSSID 1").comparator(Comparator.Compare.IS_EQUAL) e.wifiConnected = false Assert.assertFalse(t.shouldRun()) e.wifiConnected = true e.ssid = "otherSSID" Assert.assertFalse(t.shouldRun()) e.wifiConnected = true - e.ssid = "aSSID" + e.ssid = "aSSID 1" Assert.assertTrue(t.shouldRun()) - t = TriggerWifiSsid(injector).setValue("aSSID").comparator(Comparator.Compare.IS_NOT_AVAILABLE) + t = TriggerWifiSsid(injector).setValue("aSSID 1").comparator(Comparator.Compare.IS_NOT_AVAILABLE) e.wifiConnected = false Assert.assertTrue(t.shouldRun()) diff --git a/plugins/configuration/src/main/java/info/nightscout/configuration/activities/DaggerAppCompatActivityWithResult.kt b/plugins/configuration/src/main/java/info/nightscout/configuration/activities/DaggerAppCompatActivityWithResult.kt index 4480857f41..330551c210 100644 --- a/plugins/configuration/src/main/java/info/nightscout/configuration/activities/DaggerAppCompatActivityWithResult.kt +++ b/plugins/configuration/src/main/java/info/nightscout/configuration/activities/DaggerAppCompatActivityWithResult.kt @@ -33,7 +33,6 @@ open class DaggerAppCompatActivityWithResult : DaggerAppCompatActivity() { public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setTheme(info.nightscout.core.ui.R.style.AppTheme_NoActionBar) - rh.updateContext(this) compositeDisposable.add(rxBus.toObservable(EventThemeSwitch::class.java).subscribe { recreate() diff --git a/plugins/configuration/src/main/java/info/nightscout/configuration/activities/SingleFragmentActivity.kt b/plugins/configuration/src/main/java/info/nightscout/configuration/activities/SingleFragmentActivity.kt index 5292f243cf..cab20ee2e1 100644 --- a/plugins/configuration/src/main/java/info/nightscout/configuration/activities/SingleFragmentActivity.kt +++ b/plugins/configuration/src/main/java/info/nightscout/configuration/activities/SingleFragmentActivity.kt @@ -1,12 +1,10 @@ package info.nightscout.configuration.activities -import android.content.Context import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem import info.nightscout.configuration.R -import info.nightscout.core.ui.locale.LocaleHelper import info.nightscout.interfaces.plugin.ActivePlugin import info.nightscout.interfaces.plugin.PluginBase import info.nightscout.interfaces.protection.ProtectionCheck @@ -60,8 +58,4 @@ class SingleFragmentActivity : DaggerAppCompatActivityWithResult() { if (plugin?.preferencesId != -1) menuInflater.inflate(R.menu.menu_single_fragment, menu) return super.onCreateOptionsMenu(menu) } - - override fun attachBaseContext(newBase: Context) { - super.attachBaseContext(LocaleHelper.wrap(newBase)) - } } \ No newline at end of file diff --git a/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/activities/LogSettingActivity.kt b/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/activities/LogSettingActivity.kt index 890dcff920..0c88ce6107 100644 --- a/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/activities/LogSettingActivity.kt +++ b/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/activities/LogSettingActivity.kt @@ -5,14 +5,14 @@ import android.view.View import android.widget.CheckBox import android.widget.LinearLayout import android.widget.TextView -import dagger.android.support.DaggerAppCompatActivity import info.nightscout.configuration.R import info.nightscout.configuration.databinding.ActivityLogsettingBinding +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.rx.interfaces.L import info.nightscout.rx.interfaces.LogElement import javax.inject.Inject -class LogSettingActivity : DaggerAppCompatActivity() { +class LogSettingActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var l: L diff --git a/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/activities/PrefImportListActivity.kt b/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/activities/PrefImportListActivity.kt index f3e3d25666..27ec178c8d 100644 --- a/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/activities/PrefImportListActivity.kt +++ b/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/activities/PrefImportListActivity.kt @@ -1,6 +1,5 @@ package info.nightscout.configuration.maintenance.activities -import android.content.Context import android.content.Intent import android.os.Bundle import android.view.LayoutInflater @@ -10,12 +9,11 @@ import android.view.ViewGroup import androidx.fragment.app.FragmentActivity import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import dagger.android.support.DaggerAppCompatActivity import info.nightscout.configuration.R import info.nightscout.configuration.databinding.MaintenanceImportListActivityBinding import info.nightscout.configuration.databinding.MaintenanceImportListItemBinding import info.nightscout.configuration.maintenance.PrefsFileContract -import info.nightscout.core.ui.locale.LocaleHelper +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.interfaces.maintenance.PrefFileListProvider import info.nightscout.interfaces.maintenance.PrefsFile import info.nightscout.interfaces.maintenance.PrefsMetadataKey @@ -23,7 +21,7 @@ import info.nightscout.interfaces.maintenance.PrefsStatus import info.nightscout.shared.interfaces.ResourceHelper import javax.inject.Inject -class PrefImportListActivity : DaggerAppCompatActivity() { +class PrefImportListActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var rh: ResourceHelper @Inject lateinit var prefFileListProvider: PrefFileListProvider @@ -116,8 +114,4 @@ class PrefImportListActivity : DaggerAppCompatActivity() { } return super.onOptionsItemSelected(item) } - - override fun attachBaseContext(newBase: Context) { - super.attachBaseContext(LocaleHelper.wrap(newBase)) - } } \ No newline at end of file diff --git a/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/elements/SWEditNumberWithUnits.kt b/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/elements/SWEditNumberWithUnits.kt index a85d4ffe99..e03b1e1785 100644 --- a/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/elements/SWEditNumberWithUnits.kt +++ b/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/elements/SWEditNumberWithUnits.kt @@ -9,6 +9,7 @@ import android.widget.TextView import dagger.android.HasAndroidInjector import info.nightscout.configuration.setupwizard.SWNumberValidator import info.nightscout.core.ui.elements.NumberPicker +import info.nightscout.interfaces.Constants import info.nightscout.interfaces.GlucoseUnit import info.nightscout.interfaces.profile.Profile import info.nightscout.interfaces.profile.ProfileFunction @@ -16,12 +17,15 @@ import info.nightscout.shared.SafeParse import java.text.DecimalFormat import javax.inject.Inject -class SWEditNumberWithUnits(injector: HasAndroidInjector, private val init: Double, private val min: Double, private val max: Double) : SWItem(injector, Type.UNIT_NUMBER) { +class SWEditNumberWithUnits(injector: HasAndroidInjector, private val init: Double, private val minMmol: Double, private val maxMmol: Double) : SWItem(injector, Type.UNIT_NUMBER) { @Inject lateinit var profileFunction: ProfileFunction private val validator: SWNumberValidator = - SWNumberValidator { value -> value in min..max } + if (profileFunction.getUnits() == GlucoseUnit.MMOL) + SWNumberValidator { value -> value in minMmol..maxMmol } + else + SWNumberValidator { value -> value in minMmol * Constants.MMOLL_TO_MGDL..maxMmol * Constants.MMOLL_TO_MGDL } private var updateDelay = 0 override fun generateDialog(layout: LinearLayout) { @@ -44,7 +48,10 @@ class SWEditNumberWithUnits(injector: HasAndroidInjector, private val init: Doub var initValue = sp.getDouble(preferenceId, init) initValue = Profile.toCurrentUnits(profileFunction.getUnits(), initValue) val numberPicker = NumberPicker(context) - if (profileFunction.getUnits() == GlucoseUnit.MMOL) numberPicker.setParams(initValue, min, max, 0.1, DecimalFormat("0.0"), false, null, watcher) else numberPicker.setParams(initValue, min * 18, max * 18, 1.0, DecimalFormat("0"), false, null, watcher) + if (profileFunction.getUnits() == GlucoseUnit.MMOL) + numberPicker.setParams(initValue, minMmol, maxMmol, 0.1, DecimalFormat("0.0"), false, null, watcher) + else + numberPicker.setParams(initValue, minMmol * Constants.MMOLL_TO_MGDL, maxMmol * Constants.MMOLL_TO_MGDL, 1.0, DecimalFormat("0"), false, null, watcher) layout.addView(numberPicker) val c = TextView(context) diff --git a/plugins/configuration/src/main/res/values-it-rIT/strings.xml b/plugins/configuration/src/main/res/values-it-rIT/strings.xml index ca045738e9..d280685dfe 100644 --- a/plugins/configuration/src/main/res/values-it-rIT/strings.xml +++ b/plugins/configuration/src/main/res/values-it-rIT/strings.xml @@ -25,6 +25,7 @@ La password master viene usata per la crittografia del backup e per gestire la sicurezza nell\'applicazione. Ricordala o conservala in un luogo sicuro. Password Master corrente Usa i valori del cibo più abbondante che mangi di solito\n + Sincronizza i dati nel cloud. Puoi saltare questa parte ora, ma non sarai in grado di superare gli obiettivi fino a quando non avrai impostato il client Nightscout o Tidepool. Stato: Nome paziente Fornisci il nome del paziente o il nickname per distinguere questa configurazione tra altre @@ -64,6 +65,7 @@ Generale Questi sono alcuni plugin generali che potresti trovare utili. Sincronizzazione + Plugin caricamento e sincronizzazione dati. Quali vincoli sono applicati? Loop Usalo per attivare l\'integrazione del loop di AAPS. diff --git a/plugins/configuration/src/main/res/values-lt-rLT/strings.xml b/plugins/configuration/src/main/res/values-lt-rLT/strings.xml index e8238434f8..32935ef321 100644 --- a/plugins/configuration/src/main/res/values-lt-rLT/strings.xml +++ b/plugins/configuration/src/main/res/values-lt-rLT/strings.xml @@ -8,7 +8,7 @@ UŽBAIGTI Sveiki atvykę į sąrankos vedlį. Jis lydės jus per visą nustatymų procesą\n Licencinė sutartis su vartotoju - PROGRAMA NEGALI BŪTI NAUDOJAMA GYDYMO SPRENDIMAMS PRIIMTI. PAGAL GALIOJANČIUS ĮSTATYMUS PROGRAMA NESUTEIKIA JOKIŲ GARANTIJŲ VARTOTOJUI. JEI RAŠTU NENURODYTA KITAIP, PROGRAMOS AUTORINIŲ TEISIŲ TURĖTOJAS IR/AR KITOS ŠALYS PATEIKIA PROGRAMĄ \"KAIP YRA\" BE JOKIŲ GARANTIJŲ, TIEK AKIVAIZDŽIŲ, TIEK NUMANOMŲ, ĮSKAITANT, BET NEAPSIRIBOJANT GALIMYBĘ NAUDOTI PROGRAMĄ KOMERCINIAMS AR KITOKIEMS DALINIAMS TIKSLAMS. RIZIKĄ, KYLANČIĄ NAUDOJANT PROGRAMĄ PRISIIMA PATS VARTOTOJAS. JEI PROGRAMA VEIKIA NETINKAMAI, IŠLAIDOS, SUSIJUSIOS SU PROGRAMOS SERVISU, TAISYMU AR KOREGAVIMU, TENKA VARTOTOJUI. + PROGRAMA NEGALI BŪTI NAUDOJAMA GYDYMO SPRENDIMAMS PRIIMTI. PAGAL GALIOJANČIUS ĮSTATYMUS PROGRAMA NESUTEIKIA JOKIŲ GARANTIJŲ VARTOTOJUI. JEI RAŠTU NENURODYTA KITAIP, PROGRAMOS AUTORINIŲ TEISIŲ TURĖTOJAS IR/AR KITOS ŠALYS PATEIKIA PROGRAMĄ \"KAIP YRA\" BE JOKIŲ GARANTIJŲ, TIEK AKIVAIZDŽIŲ, TIEK NUMANOMŲ, ĮSKAITANT, BET NEAPSIRIBOJANT, GALIMYBĘ NAUDOTI PROGRAMĄ KOMERCINIAMS AR KITOKIEMS DALINIAMS TIKSLAMS. RIZIKĄ, KYLANČIĄ NAUDOJANT PROGRAMĄ PRISIIMA PATS VARTOTOJAS. JEI PROGRAMA VEIKIA NETINKAMAI, IŠLAIDOS, SUSIJUSIOS SU PROGRAMOS SERVISU, TAISYMU AR KOREGAVIMU, TENKA VARTOTOJUI. SUPRATAU IR SUTINKU Ekrano nustatymai Žemoji riba @@ -39,8 +39,8 @@ Jautrumo įskiepis yra naudojamas jautrumo aptikimui ir AAO skaičiavimams. Daugiau informacijos: https://wiki.aaps.app/en/latest/Configuration/Sensitivity-detection-and-COB.html Pasirinkite vieną iš esamų algoritmų. Jie yra išdėstyti nuo seniausio iki naujausio. Naujesni algoritmai yra galingesni ir agresyvesni. Taigi jei esate naujas vartotojas, geriau pradėti nuo AMA, o ne naujesnio. Nepamirškite perskaityti dokumentaciją ir sukonfigūruoti jį prieš naudojant. - Sukonfigūruokite RileyLink žemiau. Pasirinkę RileyLink, galėsite tęsti diegimą, kai tik RileyLink būsena bus „Prisijungta“. Tai gali užtrukti minutę.\n - Pastaba: Sąranką galite tęsti, kai tik inicijuojama pompa.\n + Nustatykite RileyLink. Pasirinkę RileyLink, galėsite tęsti sąranką, kai tik RileyLink statusas bus „Prisijungta“. Tai gali užtrukti apie minutę.\n + Pastaba: Sąranką galėsite tęsti, kai prijungsite pompą.\n Pradėti Jūsų pirmąjį tikslą RileyLink statusas: Statuso nuskaitymas diff --git a/plugins/configuration/src/main/res/values-pl-rPL/strings.xml b/plugins/configuration/src/main/res/values-pl-rPL/strings.xml index 2a51d5575d..3f50e47b74 100644 --- a/plugins/configuration/src/main/res/values-pl-rPL/strings.xml +++ b/plugins/configuration/src/main/res/values-pl-rPL/strings.xml @@ -22,7 +22,7 @@ Zapytaj o uprawnienia %1$s potrzebuje wyłączenia z optymalizacji baterii, w celu zapewnienia poprawnego działania Znaleziono zapisane ustawienia - Hasło główne jest używane do szyfrowania kopii zapasowych i przełamania zabezpieczeń w aplikacji. Zapamiętaj je lub przechowuj w bezpiecznym miejscu. + Hasło główne jest używane do szyfrowania kopii zapasowych i przełamania zabezpieczeń w aplikacji. Zapamiętaj je i przechowuj w bezpiecznym miejscu (np.: managerze haseł). Aktualne hasło główne Użyj wartości największego jedzenia, które zazwyczaj zjadasz\n Synchronizuj dane z chmurą. Możesz teraz pominąć ten krok, ale nie będziesz w stanie przejść zadań dopóki nie skonfigurujesz połączenia z Nightscout lub Tidepool. @@ -83,7 +83,7 @@ Wersja AAPSClient nie jest zgodna z wersją AAPS. Proszę zaktualizować. Ustawienia - Zostaniesz poproszony o hasło główne, które będzie użyte do szyfrowania wyeksportowanych preferencji. + Zostaniesz poproszony o hasło główne, które będzie użyte do szyfrowania wyeksportowanych ustawień. Eksportowanie anulowane! Ustawienia NIE zostały wyeksportowane! Import anulowany! Ustawienia NIE zostały zaimportowane! Nie można zaimportować ustawień! @@ -147,7 +147,7 @@ Błąd odszyfrowania, podane hasło nie może odszyfrować pliku Brak sumy kontrolnej pliku (hash), nie można zweryfikować autentyczności ustawień! Plik został zmodyfikowany po eksporcie! - Błąd odszyfrowania, nie udało się przetworzyć ustawień! + Błąd odszyfrowania, nie udało się odczytać ustawień! Błąd deszyfrowania, podane hasło jest niepoprawne lub plik ustawień został zmodyfikowany! Może się zdarzyć, że zaimportowany plik został wyeksportowany z innym hasłem głównym. Brak konfiguracji szyfrowania, format ustawień jest nieprawidłowy! Nieobsługiwany lub nieokreślony algorytm szyfrowania! diff --git a/plugins/constraints/src/main/res/layout/objectives_fragment.xml b/plugins/constraints/src/main/res/layout/objectives_fragment.xml index acd6857670..d7306a552d 100644 --- a/plugins/constraints/src/main/res/layout/objectives_fragment.xml +++ b/plugins/constraints/src/main/res/layout/objectives_fragment.xml @@ -3,7 +3,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - tools:context=".constraints.objectives.ObjectivesFragment"> + tools:context="info.nightscout.plugins.constraints.objectives.ObjectivesFragment"> Pas basale en verhoudings aan as nodig, en dan aktiveer auto-sens
1 week suksesvolle dagtyd lus met gereelde karb toevoegings Skakel bykomende nutsfunskies aan vir bedags gebruik, soos bv. SMB - Jy moet asb die wiki lees en verhoog maksIAB om SMBs te laat werk! \'n Goeie begin is maksIAB = gemiddelde ete bolus + 3 x maks daaglikse basale Per hand aksies Bereik: %1$s Stel profiel 90% vir 10 min (Lang-pers profiel naam op Oorsig) diff --git a/plugins/constraints/src/main/res/values-bg-rBG/exam.xml b/plugins/constraints/src/main/res/values-bg-rBG/exam.xml index c7667d76cd..832e8fc3c0 100644 --- a/plugins/constraints/src/main/res/values-bg-rBG/exam.xml +++ b/plugins/constraints/src/main/res/values-bg-rBG/exam.xml @@ -200,7 +200,6 @@ При превключване на 120% профил, кои отговори са верни? Целевата кръвна захар ще бъде с 20% по-висока. Базалните нива ще бъдат с 20% по-високи. - Целевата кръвна глюкоза ще остане непроменена. ISF ще бъде с 20% по-висок. Смяна на профил Ако станете 2 часа по-рано от обикновено, как трябва да уведомите AAPS за промяната в графика си? diff --git a/plugins/constraints/src/main/res/values-bg-rBG/objectives.xml b/plugins/constraints/src/main/res/values-bg-rBG/objectives.xml index 4fe20ae735..293d69d415 100644 --- a/plugins/constraints/src/main/res/values-bg-rBG/objectives.xml +++ b/plugins/constraints/src/main/res/values-bg-rBG/objectives.xml @@ -24,7 +24,6 @@ Една седмица успешно дневно използване с редовно въвеждане на въглехидрати Ако вашият резултат от autosens не се колебае около 100%, вашият профил вероятно е грешен. Добавяне на допълнителни функции за използване през деня, като SMB - Трябва да прочетете wiki и увеличите maxIOB за да може SMB да работи добре! Добро начало е maxIOB = средния болус за хранене + 3 пъти най-големия базал от профила Използването на SMB трябва да е вашата основна цел. Алгоритъмът Oref1 е създаден, за да ви помогне и с вашите болуси. Не трябва да давате пълен болус за вашата храна, а само част от него и оставете AAPS да ви даде останалото, ако е необходимо. По този начин имате по-голям диапазон за грешно изчислени въглехидрати. Знаете ли, че можете да зададете процент от резултата от болус калкулатора, за да намалите размера на болуса? Разрешаване на автоматизация Прочетете документацията за това как работи автоматизацията. Настройте първите си прости правила. Нека AAPS показва само известия. Когато сте сигурни, автоматизацията се задейства в точното време, за да заменете известяването с реални действия. (https: //androidaps.readthedocs.io/en/latest/EN/Usage/Automation.html) diff --git a/plugins/constraints/src/main/res/values-ca-rES/objectives.xml b/plugins/constraints/src/main/res/values-ca-rES/objectives.xml index 9c949ee031..837ba1f5c8 100644 --- a/plugins/constraints/src/main/res/values-ca-rES/objectives.xml +++ b/plugins/constraints/src/main/res/values-ca-rES/objectives.xml @@ -18,7 +18,6 @@ Ajustar basals i ràtios si cal, i després activar auto-sens 1 setmana d\'èxit en mode llaç tancat durant el dia, amb introducció regular de carbohidrats Activant funcions addicionals d\'ús diurn, com l\'SMB (super micro bolus) - Heu de llegir la wiki i augmentar la maxIOB per a què l\'SMB funcioni correctament! Una bona manera de començar és amb maxIOB = bolus d\'àpat mig + 3 x màxima basal diària Activant l\'automatització Activació manual Completat: %1$s diff --git a/plugins/constraints/src/main/res/values-cs-rCZ/objectives.xml b/plugins/constraints/src/main/res/values-cs-rCZ/objectives.xml index c81be3c829..09f258f852 100644 --- a/plugins/constraints/src/main/res/values-cs-rCZ/objectives.xml +++ b/plugins/constraints/src/main/res/values-cs-rCZ/objectives.xml @@ -24,7 +24,7 @@ Jeden týden úspěšného používání s běžným příjmem sacharidů Pokud váš výsledek autosens neosciluje kolem 100%, nastavení Vašeho profilu pravděpodobně špatné. Povolit další funkce pro běžné používání jako SMB - Přečíst si dokumentaci a zvýšit maximální IOB, aby mohlo SMB fungovat. Pro začátek se dá použít velikost běžného bolusu + 3x maximální denní bazál + Přečíst si dokumentaci a zvýšit maximání IOB, aby mohlo SMB fungovat. Pro začátek se dá použít velikost běžného bolusu + 3x maximální denní bazál Použití SMB je vaším cílem. Oref1 algoritmus byl navržen tak, aby vám pomohl i s vašimi bolusy. Neměli byste dávat úplný bolus pro vaše jídlo, ale jen jeho část a nechtat AAPS v případě potřeby dát zbytek. Takto máte větší rezervu pro špatně vypočtené sacharidy. Věděli jste, že můžete nastavit procento bolusové kalkulačky pro snížení velikosti bolusu? Povolení automatizace Přečtěte si na wiki, jak automatizace funguje. Nejdříve nastavte pouze jednoduchá pravidla. Namísto provádění akcí nechte AAPS zobrazovat pouze oznámení. Pokud jste si jistí, že je automatizace spouštěna v pravý čas, můžete oznámení nahradit prováděním akce. (https://wiki.aaps.app/cs/latest/Usage/Automation.html) diff --git a/plugins/constraints/src/main/res/values-da-rDK/objectives.xml b/plugins/constraints/src/main/res/values-da-rDK/objectives.xml index 2bfd196651..b044d92d50 100644 --- a/plugins/constraints/src/main/res/values-da-rDK/objectives.xml +++ b/plugins/constraints/src/main/res/values-da-rDK/objectives.xml @@ -18,7 +18,6 @@ Justér om nødvendigt basaler og forhold og aktivér derefter auto-sens 1 uges vellykket looping i dagtimerne med alle måltider tastet ind Aktivering af yderligere funktioner til brug i dagtimerne, såsom SMB - Du skal læse wikien og hæve maxIOB for at få SMB\'er til at fungere fint! En god start er maxIOB=gennemsnitlig måltidsbolus + 3 x max daglig basal Aktiverer automatisering Manuelle handlinger Udført: %1$s diff --git a/plugins/constraints/src/main/res/values-de-rDE/objectives.xml b/plugins/constraints/src/main/res/values-de-rDE/objectives.xml index 96ed2d5e6d..179a3c3f37 100644 --- a/plugins/constraints/src/main/res/values-de-rDE/objectives.xml +++ b/plugins/constraints/src/main/res/values-de-rDE/objectives.xml @@ -24,8 +24,6 @@ Loope eine Woche tagsüber mit regelmäßiger Kohlenhydrat-Eingabe Wenn Ihr Autosens Ergebnis nicht stündlich schwankt, um die angestrebten 100 % ist das verwendete Profil wahrscheinlich falsch. Aktiviere zusätzliche Funktionen für die Nutzung tagsüber wie z. B. SMB - Lies das Wiki und erhöhe maxIOB, damit der SMB gut funktioniert. Ein guter Anfang ist -die Formel maxIOB = durchschnittlicher Essensbolus + 3 x höchste Basalrate Die Verwendung von SMB ist Dein Ziel. Der Oref1-Algorithmus wurde entwickelt, um Dir auch mit Boli zu helfen. Du solltest nicht den vollen Bolus für Dein Essen geben, sondern nur einen Teil davon abgeben und es AAPS überlassen bei Bedarf den notwendigen Rest entsprechend dem Blutzuckerverlauf zugeben zu lassen. Auf diese Weise hast Du mehr Platz für falsch berechnete Kohlenhydrate. Wusstest Du, dass Du einen Prozentsatz bei Verwendung des Bolusrechners festlegen kannst, um die Größe des Bolus zu reduzieren? Automatisierung aktivieren Lies in der Dokumentation nach, wie die Automation funktioniert. Richte Dir erst einfache Regeln ein. Zunächst sollte AAPS keine Änderungen vornehmen, sondern Dir nur eine Benachrichtigung anzeigen. Wenn Du Dir sicher bist, dass die Automation im richtigen Moment angetriggert wird, dann kannst Du die Benachrichtigung durch eine Aktion ersetzen (https://androidaps.readthedocs.io/de/latest/CROWDIN/de/Usage/Automation.html) diff --git a/plugins/constraints/src/main/res/values-el-rGR/objectives.xml b/plugins/constraints/src/main/res/values-el-rGR/objectives.xml index 13add3c58c..9bb8fdf976 100644 --- a/plugins/constraints/src/main/res/values-el-rGR/objectives.xml +++ b/plugins/constraints/src/main/res/values-el-rGR/objectives.xml @@ -18,7 +18,6 @@ Ρυθμίστε τον βασικό ρυθμό και τις αναλογίες αν χρειάζεται, και μετά ενεργοποιήστε το auto-sens 1 επιτυχής εβδομάδα ημερήσιου κυκλώματος με κανονική εισαγωγή υδατανθράκων Ενεργοποιώντας επιπρόσθετα χαρακτηριστικά για χρήση κατά την ημέρα, όπως το SMB - Πρέπει να διαβάσετε τον οδηγό wiki και να αυξήσετε το ανώτατο όριο του IOB για να μπορέσει να λειτουργήσει σωστά το SMB! Μια καλή αρχή είναι μέγιστο IOB= μέσο bolus γεύματος + 3 x το μέγιστο ημερήσιο βασικού ρυθμού Ξεκινήστε χειροκίνητα Επιτεύχθηκε: %1$s Θέστε το προφίλ στο 90% για 10 λεπτά (Παρατεταμένο πάτημα του προφίλ στην Επισκόπηση) diff --git a/plugins/constraints/src/main/res/values-es-rES/objectives.xml b/plugins/constraints/src/main/res/values-es-rES/objectives.xml index e021cdc447..7ebb6b4451 100644 --- a/plugins/constraints/src/main/res/values-es-rES/objectives.xml +++ b/plugins/constraints/src/main/res/values-es-rES/objectives.xml @@ -24,7 +24,7 @@ Una semana con éxito en bucle cerrado durante el día con entrada regular de carbohidratos Si el resultado de tu autosens no oscila en torno al 100% es probable que tu perfil esté equivocado. Habilitar funciones adicionales para uso durante el día como SMB - ¡Debes leer el wiki y subir maxIOB para que SMB funcione bien! Para comenzar un buen valor sería maxIOB = bolo de comida medio + 3 x máximo basal diario + Debes leer la wiki y aumentar el valor de maxIOB para que SMB funcione bien. Un buen comienzo sería maxIOB = bolo de comida promedio + 3x máxima basal diaria Utilizar SMB es su objetivo. El algoritmo Oref1 fue diseñado para ayudarle también con sus bolos. No debe dar un bolo completo para su comida, sino sólo una parte y dejar que AAPS le dé el resto si es necesario. De esta forma tendrás más espacio para los carbohidratos mal calculados. ¿Sabías que puedes establecer un porcentaje del resultado de la calculadora de bolo para reducir el tamaño del bolo? Activar automatización Lee la documentación sobre el funcionamiento de la automatización. Configure sus primeras reglas simples. En lugar de una acción, deja que AAPS muestre sólo una notificación. Cuando esté seguro de que la automatización se activa en el momento adecuado, sustituya la notificación por una acción real. (https://wiki.aaps.app/en/latest/Usage/Automation.html) diff --git a/plugins/constraints/src/main/res/values-fr-rFR/objectives.xml b/plugins/constraints/src/main/res/values-fr-rFR/objectives.xml index 1ee32b4377..5af4c7a2f4 100644 --- a/plugins/constraints/src/main/res/values-fr-rFR/objectives.xml +++ b/plugins/constraints/src/main/res/values-fr-rFR/objectives.xml @@ -24,7 +24,6 @@ 1 semaine de Boucle Fermée en journée en saisissant régulièrement les glucides Si le résultat de votre autosens n\'est pas autour de 100%, votre profil est probablement erroné. Activation de fonctionnalités supplémentaires pour l\'utilisation en journée, telles que la fonction SMB - Lisez le wiki et augmentez le maxIA pour que les SMBs fonctionnent correctement ! Un bon début est maxIA = moyenne des Bolus Repas + 3 x maximum débit Basal quotidien L\'utilisation de SMB est votre objectif. L\'algorithme Oref1 a été conçu pour vous aider également avec vos bolus. Vous ne devriez pas donner un bolus complet pour votre nourriture, mais seulement une partie de celui-ci et laisser AAPS vous donner le reste si nécessaire. De cette façon, vous avez plus de latitude pour les glucides mal calculés. Saviez-vous que vous pouvez définir un pourcentage à appliquer au résultat de la calculatrice de bolus pour réduire la taille du bolus ? Activation de l\'automatisation Lisez la documentation sur le fonctionnement de l\'automatisation fonctionne. Configurez vos premières règles simples. Au lieu de mettre une action, configurez une notification. Quand vous êtes sûr que l\'automatisation est déclenchée au bon moment, remplacez la notification par une action réelle. (https://wiki.aaps.app/fr/latest/Usage/Automation.html) diff --git a/plugins/constraints/src/main/res/values-it-rIT/objectives.xml b/plugins/constraints/src/main/res/values-it-rIT/objectives.xml index 044992b4ba..16c741052f 100644 --- a/plugins/constraints/src/main/res/values-it-rIT/objectives.xml +++ b/plugins/constraints/src/main/res/values-it-rIT/objectives.xml @@ -19,19 +19,23 @@ Impostare MaxIOB a zero previene l\'ipoglicemia e non aggiungerà più insulina al di sopra della velocità basale (eccetto in caso di IOB negativo) Regolazione del loop chiuso, aumentando max IOB al di sopra di 0 e abbassando gradualmente i target glicemici Esegui l\'applicazione per alcuni giorni e almeno una notte senza allarmi di glicemia bassa, prima di abbassare il target glicemico + Aggiorna MaxIOB in base alla crescita del bambino. Non permettere al sistema di dare più insulina di quanto si può coprire con il cibo è una cattiva idea quanto impostare un valore davvero alto. Adatta basali e rapporti se necessario, quindi attiva auto-sens 1 settimana di looping diurno con inserimento regolare dei carboidrati, eseguito con successo Se il risultato di autosens non è oscillante intorno al 100% il tuo profilo probabilmente è errato. Abilitazione funzioni aggiuntive per l\'uso diurno, come SMB È necessario leggere il wiki e aumentare maxIOB affinché le azioni di SMB funzionino adeguatamente! Un buon inizio è maxIOB = media bolo posto + 3 x max basale giornaliera + Usare SMB è il tuo obiettivo. L\'algoritmo Oref1 è stato progettato per aiutarti anche con i tuoi boli. Non dovresti erogare un bolo completo per il tuo cibo, ma solo una parte di esso e lasciare che AAPS ti dia il resto se necessario. In questo modo hai più spazio di azione per i carboidrati mal calcolati. Lo sapevi che puoi impostare una percentuale del risultato del calcolatore per ridurre la dimensione del bolo? Abilitazione automazione Leggi la documentazione su come funziona l\'automazione. Configura le tue prime semplici regole. Consenti ad AAPS solo di visualizzare una notifica, non di eseguire un\'azione. Quando sei sicuro che l\'automazione viene attivata al momento giusto, sostituisci la notifica con un\'azione reale. (https://wiki.aaps.app/en/latest/Usage/Automation.html) + L\'automazione può essere un buon \"servitore\" ma anche un cattivo \"padrone\". Non abusarne. Non cercare di sostituire l\'algoritmo di base. Verifica lo stato dell\'automazione con un messaggio prima dell\'uso. BG disponibile in NS o Tidepool Stato micro disponibile in NS o Tidepool Attivazioni manuali Completato: %1$s Impara a controllare AAPS Esegui varie azioni in AAPS + Hai imparato come gestire le basi di AAPS. I controlli principali sono nella schermata Panoramica accessibile con un click o un click lungo. Ci sono controlli aggiuntivi nella schermata Azioni. Dovresti sapere come mettere la schermata di un plugin nel menu scorrevole in alto o lasciare che il plugin sia visualizzato nell\'elenco dei plugin attivati in alto a sinistra. Imposta il profilo \"90%\" per 10 min (premi a lungo sul nome profilo nella sezione Panoramica) Simula la doccia. Disconnetti il micro per 1h (premi a lungo su Loop aperto) ... e riconnetti allo stesso modo @@ -47,13 +51,21 @@ DIA nell\'attività di looping ha un significato diverso (tempo fino a quando tutta l’insulina viene assorbita) rispetto alla classica terapia con il micro (tempo fino a quando la maggior parte dell’insulina viene assorbita). Hai imparato il significato del valore ISF e come influisce sulla quantità di insulina utilizzata per la correzione della glicemia. Hai imparato il significato del valore IC e come influisce sulla quantità di insulina necessaria per \"coprire\" i CHO. + Il target temporaneo \"ipoglicemia\" è usato solo per prevenire la sovracorrezione dopo l\'ipo, quando c\'è di solito un IOB negativo. Dovrebbero essere valutate ulteriori azioni per evitare che questa situazione si ripeta in futuro. + L\'utilizzo della percentuale non influisce sui target glicemici. Ma basale, ISF e IC sono regolati per fornire più insulina (sopra il 100%%) o meno insulina (sotto il 100%%). Usando il timeshift nel cambio profilo puoi adattare il profilo circadiano a irregolarità come il risveglio tardivo. + La modifica del profilo in sé non è sufficiente per apportare una modifica. Hai ancora bisogno di fare un cambio profilo per attivare le modifiche. + Dovresti ridurre la quantità di insulina nell’organismo almeno 1 ora prima di iniziare l’esercizio fisico selezionando il profilo al di sotto del 100%. + L\'impostazione di un target temporaneo \"alto\" almeno 1 ora prima dell\'esercizio fisico aiuta anche a ridurre la quantità di insulina nel corpo. Quando non puoi fidarti dei valori CGM non dovresti usare il loop senza supervisione. AAPS deve sapere che il micro non è connesso per calcolare IOB correttamente. Far sapere quale insulina è nella micro è necessario perché influisce sul calcolo di IOB. Il rilevamento della sensibilità fa parte dell\'algoritmo che consente di adattare il dosaggio di insulina a diverse situazioni. I tuoi progressi negli Obiettivi sono memorizzati insieme alle altre impostazioni e dovrebbe essere eseguito il backup. + Manteni sempre le impostazioni esportate e l\'APK generato anche al di fuori del telefono. Il telefono può essere perso, danneggiato ecc. Un buon posto può essere qualsiasi cloud accessibile dal telefono. In questo caso puoi ripristinare AAPS su un altro dispositivo in pochi minuti. Una buona pratica è anche memorizzare in un posto sicuro la password master usata. Senza di essa il backup è inutile. Lo stesso vale per le chiavi di firma (file .jks) e le password usate per costruire AAPS. Il resto non è importante - può essere scaricato di nuovo e in qualsiasi momento da internet. Se hai fatto inserimenti errati in AAPS o non corrispondenti alla realtà, risolvi al più presto. Gli errori potrebbero portare a sovradosaggio. AAPS deve essere sempre informato nel migliore modo possibile riguardo carboidrati, insulina ecc. + Se stai verificando che AAPS funzioni bene per te, guardare il grafico IOB può darti un sacco di informazioni utili. + Il calcolo di COB dipende dalla relazione tra ISF a IC. Aumentare ISF o diminuire IC porterà a tempi di assorbimento più lunghi. Ma il cambio profilo in percentuale modifica entrambi i valori contemporaneamente e quindi la durata dell\'assorbimento non ne risente. Sono ammessi solo grammi per l\'inserimento di carboidrati. E-carbs è un sostituto del bolo esteso nel mondo dei microinfusori. Monitoraggio e controllo remoto sono possibili, ma non tutte le funzionalità devono essere disponibili da remoto. diff --git a/plugins/constraints/src/main/res/values-iw-rIL/objectives.xml b/plugins/constraints/src/main/res/values-iw-rIL/objectives.xml index c51b02ab61..c3f12c8b5b 100644 --- a/plugins/constraints/src/main/res/values-iw-rIL/objectives.xml +++ b/plugins/constraints/src/main/res/values-iw-rIL/objectives.xml @@ -24,7 +24,7 @@ שבוע של הפעלה מוצלחת של הלולאה במשך שעות היום, עם רישום ערכי פחמימות בקביעות אם תוצאת הזיהוי האוטומטי שלכם לא מתנודדת סביב 100%, כנראה שהפרופיל טעון שיפור. הפעלת פונקציות נוספות לשימוש במשך היום, כגון סופר מיקרו בולוסים (SMB) - חובה לקרוא את הויקי ולהעלות את ערך ה-maxIOB כדי להפעיל את ה- SMB כראוי. התחלה טובה תהיה maxIOB=בולוס ממוצע + 3 פעמים ערך הבזאלי היומי המקסימלי + חובה לקרוא את הויקי ולהעלות את ערך האינסולין הפעיל המרבי (maxIOB) כדי להפעיל את ה- SMB כראוי. התחלה טובה תהיה maxIOB=בולוס ארוחה טיפוסי + 3 פעמים ערך הבזאלי המקסימלי ביממה שימוש ב-SMB הוא המטרה שלכם. אלגוריתם Oref1 תוכנן לעזור גם עם הבולוסים שלכם. אתם לא צריכים להזריק בולוס מלא למזון שלכם אלא רק על חלק ממנו ולתת ל-AAPS לטפל בשאר במידת הצורך. כך קל יותר להכיל טעויות בספירת פחמימות. הידעתם שאפשר להגדיר אחוז מתוצאת מחשבון הבולוס כדי להקטין את גודל הבולוס? הפעלת אוטומציה קראו את המסמכים על אופן הפעולה של אוטומציות. הגדירו את התנאים הפשוטים הראשונים שלכם. במקום פעולה תנו ל-AAPS להציג הודעה בלבד. כאשר אתם בטוחים שהאוטומציה מופעלת בזמן הנכון, החליפו את ההודעה בפעולה אמיתית. (https://wiki.aaps.app/he/latest/Usage/Automation.html) diff --git a/plugins/constraints/src/main/res/values-ko-rKR/objectives.xml b/plugins/constraints/src/main/res/values-ko-rKR/objectives.xml index 38b3fa314d..b5eb35dfdf 100644 --- a/plugins/constraints/src/main/res/values-ko-rKR/objectives.xml +++ b/plugins/constraints/src/main/res/values-ko-rKR/objectives.xml @@ -18,7 +18,6 @@ 필요하면 Basal과 비율을 조절하고, auto-sens를 활성화합니다 섭취한 탄수화물양을 입력하고 1주일동안 낮시간대에 loop를 성공적으로 사용하여 봅니다 낮시간대에 SMB(Super Micro Bolus)같은 추가기능을 활성화해 사용해봅니다 - SMB가 잘 작동하게 하기위해서 wiki를 반드시 읽은 다음 maxIOB 값을 올려보세요! maxIOB=평균 식사 Bolus + 3 x 최대하루 Basal이면 적당한 시작값입니다 자동화 사용 수동 주입 완료: %1$s diff --git a/plugins/constraints/src/main/res/values-lt-rLT/objectives.xml b/plugins/constraints/src/main/res/values-lt-rLT/objectives.xml index 68484c1257..8f6d465296 100644 --- a/plugins/constraints/src/main/res/values-lt-rLT/objectives.xml +++ b/plugins/constraints/src/main/res/values-lt-rLT/objectives.xml @@ -24,13 +24,12 @@ 1 savaitę praleiskite sėkmingai naudodami uždarąjį ciklą dienos metu ir įvesdami visus valgomus angliavandenius Jei Jūsų autosens rezultatai nesvyruoja apie 100%, tikėtina, kad profilis turi klaidų. Dienos metu aktyvuokite papildomas funkcijas, tokias kaip SMB (Super Mikro Bolusas) - Norėdami, kad SMB veiktų gerai, turite perskaityti dokumentaciją ir padidinti max AIO! Pradžiai patartina skaičiuoti taip: maxAIO=(didžiausia valandinė bazė x 3) + vidutinis bolusas SMB yra Jūsų tikslas. Oref1 algoritmas sukurtas taip, kad taptų Jums pagalba su bolusavimu. Jūs galite nesileisti viso boluso maistui, susileiskite dalį, o AAPS padarys likusį darbą, jei reikės. Taip išvengsite klaidų dėl neteisingai apskaičiuotų AV. Ar žinote, kad galite skaičiuotuve nurodyti, kiek % apskaičiuoto boluso suleisti? Automatizavimo įjungimas Perskaitykite dokumentaciją apie automatizacijos veikimą. Nustatykite kelias paprastas taisykles. Nustatykite, kad AAPS tik parodytų pranešimą, o ne atliktų realų veiksmą. Kai įsitikinsite, kad automatizacija suveikia reikalingu metu, vietoj pranešimo įjunkite veiksmą. (https://wiki.aaps.app/en/latest/Usage/Automation.html) Automatizacija gali būti lazda su dviem galais. Nepiktnaudžiaukite. Nebandykite keisti ją aprašančių algoritmų. Prieš įgalindami veiksmą, išbandykite rezultatą su pranešimu. Rezultatas labai priklauso nuo sąlygų eiliškumo. Glikemija matoma NS arba Tidepool - Pompos būsena matoma NS arba Tidepool + Pompos statusas matomas NS arba Tidepool Įvesti rankiniu būdu Įvykdyta: %1$s Išmokite valdyti AAPS @@ -40,7 +39,7 @@ Imituokite maudynes duše. Atjunkite pompą 1 valandai (ilgai paspauskite Atviras Ciklas) ... ir iš naujo prisijunkite tuo pačiu būdu Nustatykite 10 min trukmės laikiną tikslą (ilgai spausti ant dabartinio tikslo) - Konfigūracijoje įjunkite Veiksmų įskiepį, nustatykite jį matomą ir jo turinio rodymą viršutiniame meniu + Konfigūracijoje įjunkite Veiksmų įskiepį, padarykite jį ir jo turinį matomą viršutiniame meniu Parodyti Ciklo įskiepio turinį Panaudokite vaizdo dydžio keitimo funkciją ilgai spaudžiant ant glikemijos kreivės Patvirtinkite savo žinias diff --git a/plugins/constraints/src/main/res/values-nl-rNL/objectives.xml b/plugins/constraints/src/main/res/values-nl-rNL/objectives.xml index 79b0471c3c..15979d6b96 100644 --- a/plugins/constraints/src/main/res/values-nl-rNL/objectives.xml +++ b/plugins/constraints/src/main/res/values-nl-rNL/objectives.xml @@ -24,7 +24,6 @@ Gedurende 1 week succesvol overdag loopen met regelmatige invoer van koolhydraten Als het resultaat van autosens zich niet rond de 100% beweegt dan is je profiel waarschijnlijk niet juist. Activeren van extra functies overdag zoals SMB (super micro bolus) - Lees de wiki en verhoog maxIOB om SMB goed werkend te krijgen. Een goed begin is maxIOB=gemiddelde maaltijdbolus + 3 x max dagelijkse basaal Het gebruik van SMB is het uiteindelijke doel. Het Oref1 algoritme is ontworpen om je ook te helpen met je bolussen. Je hoeft geen volledige bolus te geven voor jouw voeding, maar slechts een deel ervan en AAPS zal de rest geven als dat nodig is. Op deze manier heb je meer ruimte voor verkeerd berekende koolhydraten. Wist je dat je een percentage in de bolus calculator kan instellen om de hoeveelheid bolus te verminderen? Automatisering inschakelen Lees eerst de documentatie over hoe automatisering werkt. Maak daarna je eerste simpele regels, waarbij je in plaats van acties eerst gebruikt maakt van de notificaties. Wanneer je er dan zeker van bent, dat je automatisering op de goede momenten wordt geactiveerd, kan je de noticifcatie omzetten naar een echte actie. diff --git a/plugins/constraints/src/main/res/values-pl-rPL/objectives.xml b/plugins/constraints/src/main/res/values-pl-rPL/objectives.xml index 2d88b46316..e0f240b3fd 100644 --- a/plugins/constraints/src/main/res/values-pl-rPL/objectives.xml +++ b/plugins/constraints/src/main/res/values-pl-rPL/objectives.xml @@ -24,7 +24,6 @@ 1 tydzień sukcesu w działaniu pętli w ciągu dnia z regularnym wprowadzaniem spożywanych węglowodanów Jeśli wynik autosens nie oscyluje w okolicach 100%, prawdopodobnie Twój profil nie jest poprawny. Włączanie dodatkowych funkcji do użytku, jak SMB (Super Mikro Bolusy) - Musisz przeczytać wiki i zwiększyć maxIOB, aby SMB działało dobrze! Dobrym początkiem jest maxIOB = średni bolus + 3 x maks. dzienna dawka bazowa Używanie SMB jest Twoim celem. Algorytm Oref1 został zaprojektowany, aby pomóc Ci również w bolusach. Nie należy podawać pełnego bolusa do jedzenia, ale tylko jego część, a w razie potrzeby pozostawić resztę AAPS. W ten sposób masz więcej miejsca na błędnie obliczone węglowodany. Czy wiesz, że można w kalkulatorze ustawić procent wyniku bolusa, aby zmniejszyć rozmiar bolusa? Włączanie automatyzacji Przeczytaj dokumentację dotyczącą działania automatyzacji. Skonfiguruj swoje pierwsze proste zasady. Zamiast wykonania działania niech AAPS wyświetli tylko powiadomienie. Gdy masz pewność, że automatyzacja zostaje uruchomiona we właściwym czasie, zastąp powiadomienie rzeczywistą akcją. (https://wiki.aaps.app/en/latest/Usage/Automation.html) diff --git a/plugins/constraints/src/main/res/values-pt-rBR/objectives.xml b/plugins/constraints/src/main/res/values-pt-rBR/objectives.xml index 345c7d1f10..cda1ceecf7 100644 --- a/plugins/constraints/src/main/res/values-pt-rBR/objectives.xml +++ b/plugins/constraints/src/main/res/values-pt-rBR/objectives.xml @@ -24,7 +24,6 @@ 1 semana de looping durante o dia com sucesso com entrada regular de carboidratos Se o resultado do seu autosens não está oscilando por volta de 100% seu perfil provavelmente está errado. Activando recursos adicionais para uso durante o dia, como SMB - Deverá ler a wiki e aumentar a IA máx para que os SMBs funcionem corretamente! Inicialmente poderá considerar maxIA=média dos bólus + 3 x a basal diária máxima Usar SMB é o seu objetivo. O algoritmo Oref1 foi projetado para ajudá-lo também com seus bolus. Você não deve dar bolus completo para sua comida, apenas parte dela e deixar o AAPS lhe dar o resto, se necessário. Desta forma, você tem mais espaço para carboidratos mal calculado. Você sabia que pode definir uma porcentagem do resultado da calculadora de bolus para reduzir o tamanho do bolus? Ativando a automação Leia os documentos de como a automação funciona. Configure suas primeiras regras simples. Em vez de efetuar uma ação deixe AAPS apresentar apenas uma notificação. Quando você tiver certeza de que a automação é acionada no momento certo substitua a notificação por uma ação real. (https://wiki.aaps.app/en/latest/Usage/Automation.html) diff --git a/plugins/constraints/src/main/res/values-pt-rPT/objectives.xml b/plugins/constraints/src/main/res/values-pt-rPT/objectives.xml index 726c8b9335..6f84024807 100644 --- a/plugins/constraints/src/main/res/values-pt-rPT/objectives.xml +++ b/plugins/constraints/src/main/res/values-pt-rPT/objectives.xml @@ -18,7 +18,6 @@ Ajuste as basais e os rácios, se necessário, e, em seguida, active o auto-sens 1 semana de looping durante o dia com sucesso com entrada regular de hidratos de carbono Activando recursos adicionais para uso durante o dia, como SMB - Deverá ler a wiki e aumentar a IA máx para que os SMBs funcionem devidamente! Inicialmente poderá considerar maxIA=média dos bólus + 3 x a basal máxima A ativar a automatização Execução manual Concluído: %1$s diff --git a/plugins/constraints/src/main/res/values-ro-rRO/objectives.xml b/plugins/constraints/src/main/res/values-ro-rRO/objectives.xml index b0149d84c8..5920a1cbd7 100644 --- a/plugins/constraints/src/main/res/values-ro-rRO/objectives.xml +++ b/plugins/constraints/src/main/res/values-ro-rRO/objectives.xml @@ -18,7 +18,6 @@ Ajustarea bazalelor și a factorilor dacă este necesar și apoi activarea auto-sens O săptămână de buclă închisă încheiată cu succes în condițiile introducerii regulate a carbohidraților Se activează opțiuni pentru uzul în timpul zilei, cum ar fi SMB - Trebuie sa citiți wiki și să măriți maxIOB pentru a obține SMB corect! Un start bun este maxIOB=media bolusurilor + 3x maxima bazalei din zi Activarea automatizării Acțiuni manuale Îndeplinit: %1$s diff --git a/plugins/constraints/src/main/res/values-ru-rRU/objectives.xml b/plugins/constraints/src/main/res/values-ru-rRU/objectives.xml index 2192e7ab64..4c7881c0f1 100644 --- a/plugins/constraints/src/main/res/values-ru-rRU/objectives.xml +++ b/plugins/constraints/src/main/res/values-ru-rRU/objectives.xml @@ -5,8 +5,8 @@ Цель %1$d Цель %1$d не начата Цель %1$d не завершена - Настройка визуализации и мониторинга, анализ базала и коэффициентов - Убедитесь, что величина ГК и данные по инсулину помпы передаются в Nightscout + Настройка интеграции с Nightscout, анализ базала и коэффициентов + Убедитесь, что данные о гликемии и подаваемом AAPSом инсулине отображаются в Nightscout и актуальны Вы сделали базовую настройку экосистемы AAPS. Nightscout не нужен для работы AAPS, но может быть полезен для отчетов и мониторинга других пациентов. Необходимости в постоянном подключении к NS нет, если вы используете NS только для себя. Вы можете установить загрузку, например, только на домашний wifi, чтобы сэкономить заряд батареи. Старт незамкнутого цикла Начинайте работу в режиме незамкнутого цикла и ручной подстройки величины временного базала. Установите и применяйте временные цели и временные цели по умолчанию (напр. углеводы при нагрузке или купировании гипо) @@ -14,9 +14,9 @@ Глубже понимаем работу системы в режиме незамкнутого цикла, включая ее рекомендации по временным базалам На основе накопленного опыта, определяем максимальную величину базала и задаем ее в помпе и в настройки AndroidAPS Примите меры предосторожности и корректируйте, если необходимо, параметры безопасности. - Начинаем замыкать цикл с прекращением подачи инсулина при низком значении Ск (режим Low Glucose Suspend) + Начинаем замыкать цикл с прекращением подачи инсулина при низком значении ГК (режим Low Glucose Suspend) Работа в замкнутом цикле с макс активным инсулином IOB = 0 на протяжении нескольких дней избегая событий типа приостановка на низких ГК Low Suspend - Установка MaxIOB на нуль предотвращает от гипо и не добавит инсулина выше базальной скорости (за исключением отрицательных значений IOB) + Установка MaxIOB на нуль предотвращает гипо и не добавит инсулина выше базальной скорости (за исключением отрицательных значений IOB) Настройка замкнутого цикла с поднятием макс величины IOB выше 0 и постепенным понижением целевой ГК Работа несколько дней и по кр мере одну ночь без срабатывания оповещений о низкой ГК Обновляйте MaxIOB по мере взросления ребенка. Не допускайте, чтобы система подавала больше инсулина, чем вы можете покрыть едой = действительно, плохая идея выставлять высокие значения. @@ -24,7 +24,7 @@ 1 неделя успешной дневной работы с регулярным введением углеводов Если результаты autosense не колеблются около 100%, то возможно ваш профиль неверный. Активация таких доп функций для дневного времени как супер микро болюс SMB - Прочтите wiki и увеличьте maxIOB чтобы супер микро болюс SMB заработал как положено! Хорошее начало – maxIOB = средний болюс на еду + троекратный максимальный суточный базал + Прочтите статью wiki и увеличивайте maxIOB так, чтобы SMB заработал как положено! Для начала рассчитайте maxIOB как средний болюс на еду + макс. суточная базальная скорость*3 Ваша цель - это использование СМБ. Алгоритм Oref1 предназначен также, чтобы помочь вам с болюсами. Вы не должны подавать полный болюс на еду, а только часть его, предоставив AAPS подать при необходимости остальную часть болюса. Таким образом вы можете иметь большее пространство для ошибки в подсчетах углеводов. Вы знаете, что можно установить проценты для уменьшения размера болюса? Включение автоматизации Прочтите документацию по автоматизации. Настройте свои первые простые правила. Вместо действия позвольте AAPS только выводить уведомления. Если вы уверены, что автоматизация инициируется в нужное время, замените уведомление реальными действиями. (https://wiki.aaps.app/en/latest/Usage/Automation.html) @@ -49,9 +49,9 @@ AAPS может работать в автономном режиме. Следует пересмотреть и обновить профиль. Лучший профиль = лучшие результаты. Продолжительность действия инсулина DIA в AAPS имеет иное значение (время до полного поглощения инсулина), чем в классической помповой терапии (время до поглощения основной массы инсулина). - Вы изучили понятие чувствительность к инсулину ISF, и как она влияет на коррекцию ГК. - Вы изучили понятие углеводный коэффициент IC и как он влияет на количество инсулина, необходимого для покрытия углеводов. - Временная цель Hypo используется только для предотвращения чрезмерной коррекции после гипо, когда накапливается отрицательный IOB. Необходимо принять дополнительные меры, чтобы предотвратить ситуацию в будущем. + Вы изучили понятие чувствительность к инсулину ISF и то, как она влияет на коррекцию ГК. + Вы изучили понятие углеводный коэффициент IC и то, как он влияет на количество инсулина, необходимого для покрытия углеводов. + Временная цель Гипо используется только для предотвращения чрезмерной коррекции после гипо, когда накапливается отрицательный IOB. Необходимо принять дополнительные меры, чтобы предотвратить ситуацию в будущем. Использование процентов не повлияет на целевую ГК, а только на базу, ISF и IC таким образом, чтобы дать больше инсулина (выше 100%%) или меньше инсулина (ниже 100%%). Используя сдвиг по времени, можно адаптировать профиль к нерегулярным событиям типа позднего пробуждения. Редактирование профиля не является достаточным для внесения изменений. Все еще нужно переключиться на этот профиль и активировать изменения. diff --git a/plugins/constraints/src/main/res/values-sv-rSE/objectives.xml b/plugins/constraints/src/main/res/values-sv-rSE/objectives.xml index f1d14052e0..71e5017bbe 100644 --- a/plugins/constraints/src/main/res/values-sv-rSE/objectives.xml +++ b/plugins/constraints/src/main/res/values-sv-rSE/objectives.xml @@ -18,7 +18,6 @@ Justera basaler och kvoter om det behövs. Aktivera sedan autosens 1 veckas lyckad looping dagtid, där alla måltider lagts in Aktiverar ytterligare funktioner för användning dagtid, t ex SMB - Du måste läsa på wikin och öka max IOB för att få SMB att fungera bra. En bra start är att sätta max IOB till din genomsnittliga måltidsbolus plus 3 gånger den högsta basalen du har under ett dygn Aktivera automatisering Manuella justeringar Utfört: %1$s diff --git a/plugins/constraints/src/main/res/values-zh-rCN/objectives.xml b/plugins/constraints/src/main/res/values-zh-rCN/objectives.xml index f95d0957ee..79656449e8 100644 --- a/plugins/constraints/src/main/res/values-zh-rCN/objectives.xml +++ b/plugins/constraints/src/main/res/values-zh-rCN/objectives.xml @@ -18,7 +18,6 @@ 必要时调整胰岛素基础率和比率,然后再启用auto-sens胰岛素敏感系数自动调整功能 规律的进行碳水记录,成功在一个星期的白天开启闭环 在白天启用额外的功能,例如微型大剂量 SMB - 你应该阅读wiki指南,以调整 maxIOB(活性胰岛素的最大值) 设置,让SMB(微型大剂量) 更好的工作。建议是 maxIOB值=餐时剂量的平均值+基础率的最大值的3倍。 启用自动操作功能插件 手动执行 已完成: %1$s diff --git a/plugins/constraints/src/main/res/values/objectives.xml b/plugins/constraints/src/main/res/values/objectives.xml index 39343a8b62..c2913b368e 100644 --- a/plugins/constraints/src/main/res/values/objectives.xml +++ b/plugins/constraints/src/main/res/values/objectives.xml @@ -26,7 +26,7 @@ 1 week successful daytime looping with regular carb entry If your autosens result is not oscillating around 100% your profile probably wrong. Enabling additional features for daytime use, such as SMB - You must read the wiki and rise maxIOB to get SMBs working fine! A good start is maxIOB=average mealbolus + 3 x max daily basal + You must read the wiki and raise maxIOB to get SMBs working fine! A good start is maxIOB=average mealbolus + 3 x max daily basal Using SMB is your goal. Oref1 algorithm was designed to help you with your boluses as well. You should not give full bolus for your food but only part of it and let AAPS give you the rest if needed. This way you have more space for miscalculated carbs. Did you know that you can set a percentage of bolus calculator result to reduce the size of bolus? 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) diff --git a/plugins/main/src/main/java/info/nightscout/plugins/general/actions/ActionsFragment.kt b/plugins/main/src/main/java/info/nightscout/plugins/general/actions/ActionsFragment.kt index 545edfee91..b6b53de968 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/general/actions/ActionsFragment.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/general/actions/ActionsFragment.kt @@ -1,9 +1,7 @@ package info.nightscout.plugins.general.actions -import android.content.Context import android.content.Intent import android.os.Bundle -import android.util.DisplayMetrics import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -59,7 +57,6 @@ class ActionsFragment : DaggerFragment() { @Inject lateinit var sp: SP @Inject lateinit var dateUtil: DateUtil @Inject lateinit var profileFunction: ProfileFunction - @Inject lateinit var ctx: Context @Inject lateinit var rh: ResourceHelper @Inject lateinit var statusLightHandler: StatusLightHandler @Inject lateinit var fabricPrivacy: FabricPrivacy @@ -78,31 +75,22 @@ class ActionsFragment : DaggerFragment() { private val pumpCustomActions = HashMap() private val pumpCustomButtons = ArrayList() - private lateinit var dm: DisplayMetrics private var _binding: ActionsFragmentBinding? = null // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - //check screen width - dm = DisplayMetrics() - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { - @Suppress("DEPRECATION") - activity?.display?.getRealMetrics(dm) - } else { - @Suppress("DEPRECATION") - activity?.windowManager?.defaultDisplay?.getMetrics(dm) - } - _binding = ActionsFragmentBinding.inflate(inflater, container, false) - return binding.root - } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + ActionsFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - skinProvider.activeSkin().preProcessLandscapeActionsLayout(dm, binding) + val screenWidth = activity?.window?.decorView?.width ?: 0 + val screenHeight = activity?.window?.decorView?.height ?: 0 + val isLandscape = screenHeight < screenWidth + skinProvider.activeSkin().preProcessLandscapeActionsLayout(isLandscape, binding) binding.profileSwitch.setOnClickListener { activity?.let { activity -> @@ -285,7 +273,7 @@ class ActionsFragment : DaggerFragment() { binding.tddStats.visibility = pump.pumpDescription.supportsTDDs.toVisibility() val isPatchPump = pump.pumpDescription.isPatchPump binding.status.apply { - cannulaOrPatch.text = if (isPatchPump) rh.gs(R.string.patch_pump) else rh.gs(R.string.cannula) + cannulaOrPatch.text = if (cannulaOrPatch.text.isEmpty()) "" else if (isPatchPump) rh.gs(R.string.patch_pump) else rh.gs(R.string.cannula) val imageResource = if (isPatchPump) info.nightscout.core.main.R.drawable.ic_patch_pump_outline else R.drawable.ic_cp_age_cannula cannulaOrPatch.setCompoundDrawablesWithIntrinsicBounds(imageResource, 0, 0, 0) batteryLayout.visibility = (!isPatchPump || pump.pumpDescription.useHardwareLink).toVisibility() @@ -293,7 +281,11 @@ class ActionsFragment : DaggerFragment() { cannulaUsage.visibility = isPatchPump.not().toVisibility() if (!config.NSCLIENT) { - statusLightHandler.updateStatusLights(cannulaAge, cannulaUsage, insulinAge, reservoirLevel, sensorAge, sensorLevel, pbAge, batteryLevel) + statusLightHandler.updateStatusLights( + cannulaAge, cannulaUsage, insulinAge, + reservoirLevel, sensorAge, sensorLevel, + pbAge, pbLevel + ) sensorLevelLabel.text = if (activeBgSource.sensorBatteryLevel == -1) "" else rh.gs(R.string.level_label) } else { statusLightHandler.updateStatusLights(cannulaAge, cannulaUsage, insulinAge, null, sensorAge, null, pbAge, null) diff --git a/plugins/main/src/main/java/info/nightscout/plugins/general/overview/OverviewFragment.kt b/plugins/main/src/main/java/info/nightscout/plugins/general/overview/OverviewFragment.kt index 4718a51431..e4714d1079 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/general/overview/OverviewFragment.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/general/overview/OverviewFragment.kt @@ -188,7 +188,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList smallHeight = screenHeight <= Constants.SMALL_HEIGHT val landscape = screenHeight < screenWidth - skinProvider.activeSkin().preProcessLandscapeOverviewLayout(dm, binding, landscape, rh.gb(info.nightscout.shared.R.bool.isTablet), smallHeight) + skinProvider.activeSkin().preProcessLandscapeOverviewLayout(binding, landscape, rh.gb(info.nightscout.shared.R.bool.isTablet), smallHeight) binding.nsclientCard.visibility = config.NSCLIENT.toVisibility() binding.notifications.setHasFixedSize(false) @@ -841,11 +841,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList if (it.value.originalPercentage != 100 || it.value.originalTimeshift != 0L || it.value.originalDuration != 0L) info.nightscout.core.ui.R.attr.ribbonWarningColor else info.nightscout.core.ui.R.attr.ribbonDefaultColor - } else if (it is ProfileSealed.PS) { - info.nightscout.core.ui.R.attr.ribbonDefaultColor - } else { - info.nightscout.core.ui.R.attr.ribbonDefaultColor - } + } else info.nightscout.core.ui.R.attr.ribbonDefaultColor } ?: info.nightscout.core.ui.R.attr.ribbonCriticalColor val profileTextColor = profile?.let { @@ -853,11 +849,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList if (it.value.originalPercentage != 100 || it.value.originalTimeshift != 0L || it.value.originalDuration != 0L) info.nightscout.core.ui.R.attr.ribbonTextWarningColor else info.nightscout.core.ui.R.attr.ribbonTextDefaultColor - } else if (it is ProfileSealed.PS) { - info.nightscout.core.ui.R.attr.ribbonTextDefaultColor - } else { - info.nightscout.core.ui.R.attr.ribbonTextDefaultColor - } + } else info.nightscout.core.ui.R.attr.ribbonTextDefaultColor } ?: info.nightscout.core.ui.R.attr.ribbonTextDefaultColor setRibbon(binding.activeProfile, profileTextColor, profileBackgroundColor, profileFunction.getProfileNameWithRemainingTime()) } @@ -899,14 +891,12 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList binding.statusLightsLayout.apply { cannulaOrPatch.setImageResource(if (isPatchPump) info.nightscout.core.main.R.drawable.ic_patch_pump_outline else R.drawable.ic_cp_age_cannula) cannulaOrPatch.contentDescription = rh.gs(if (isPatchPump) R.string.statuslights_patch_pump_age else R.string.statuslights_cannula_age) - cannulaOrPatch.scaleX = if (isPatchPump) 1.4f else 2f - cannulaOrPatch.scaleY = cannulaOrPatch.scaleX insulinAge.visibility = isPatchPump.not().toVisibility() batteryLayout.visibility = (!isPatchPump || pump.pumpDescription.useHardwareLink).toVisibility() pbAge.visibility = (pump.pumpDescription.isBatteryReplaceable || pump.isBatteryChangeLoggingEnabled()).toVisibility() val useBatteryLevel = (pump.model() == PumpType.OMNIPOD_EROS) || (pump.model() != PumpType.ACCU_CHEK_COMBO && pump.model() != PumpType.OMNIPOD_DASH) - batteryLevel.visibility = useBatteryLevel.toVisibility() + pbLevel.visibility = useBatteryLevel.toVisibility() statusLightsLayout.visibility = (sp.getBoolean(R.string.key_show_statuslights, true) || config.NSCLIENT).toVisibility() } statusLightHandler.updateStatusLights( @@ -917,7 +907,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList binding.statusLightsLayout.sensorAge, null, binding.statusLightsLayout.pbAge, - binding.statusLightsLayout.batteryLevel + binding.statusLightsLayout.pbLevel ) } @@ -1040,6 +1030,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList var useRatioForScale = false var useDSForScale = false var useBGIForScale = false + var useHRForScale = false when { menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true @@ -1048,6 +1039,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.HR.ordinal] -> useHRForScale = true } val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] @@ -1062,6 +1054,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList if (useDSForScale) 1.0 else 0.8, useRatioForScale ) + if (menuChartSettings[g + 1][OverviewMenus.CharType.HR.ordinal]) secondGraphData.addHeartRate(useHRForScale, if (useHRForScale) 1.0 else 0.8) // set manual x bounds to have nice steps secondGraphData.formatAxis(overviewData.fromTime, overviewData.endTime) @@ -1077,7 +1070,8 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] || menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] || menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] || - menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] + menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] || + menuChartSettings[g + 1][OverviewMenus.CharType.HR.ordinal] ).toVisibility() secondaryGraphsData[g].performUpdate() } diff --git a/plugins/main/src/main/java/info/nightscout/plugins/general/overview/OverviewMenusImpl.kt b/plugins/main/src/main/java/info/nightscout/plugins/general/overview/OverviewMenusImpl.kt index d9d1abd0a1..b4fb21b8e9 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/general/overview/OverviewMenusImpl.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/general/overview/OverviewMenusImpl.kt @@ -47,7 +47,8 @@ class OverviewMenusImpl @Inject constructor( BGI(R.string.overview_show_bgi, info.nightscout.core.ui.R.attr.bgiColor, info.nightscout.core.ui.R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.bgi_shortname), SEN(R.string.overview_show_sensitivity, info.nightscout.core.ui.R.attr.ratioColor, info.nightscout.core.ui.R.attr.menuTextColorInverse, primary = false, secondary = true, shortnameId = R.string.sensitivity_shortname), ACT(R.string.overview_show_activity, info.nightscout.core.ui.R.attr.activityColor, info.nightscout.core.ui.R.attr.menuTextColor, primary = true, secondary = false, shortnameId = R.string.activity_shortname), - DEVSLOPE(R.string.overview_show_deviation_slope, info.nightscout.core.ui.R.attr.devSlopePosColor, info.nightscout.core.ui.R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.devslope_shortname) + DEVSLOPE(R.string.overview_show_deviation_slope, info.nightscout.core.ui.R.attr.devSlopePosColor, info.nightscout.core.ui.R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.devslope_shortname), + HR(R.string.overview_show_heartRate, info.nightscout.core.ui.R.attr.heartRateColor, info.nightscout.core.ui.R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.heartRate_shortname), } companion object { @@ -202,4 +203,4 @@ class OverviewMenusImpl @Inject constructor( return -1 } -} \ No newline at end of file +} diff --git a/plugins/main/src/main/java/info/nightscout/plugins/general/overview/graphData/GraphData.kt b/plugins/main/src/main/java/info/nightscout/plugins/general/overview/graphData/GraphData.kt index 1efaf873c1..f7898f954d 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/general/overview/graphData/GraphData.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/general/overview/graphData/GraphData.kt @@ -258,5 +258,14 @@ class GraphData( // draw it graph.onDataChanged(false, false) } -} + fun addHeartRate(useForScale: Boolean, scale: Double) { + val maxHR = overviewData.heartRateGraphSeries.highestValueY + if (useForScale) { + minY = 0.0 + maxY = maxHR + } + addSeries(overviewData.heartRateGraphSeries) + overviewData.heartRateScale.multiplier = maxY * scale / maxHR + } +} diff --git a/plugins/main/src/main/java/info/nightscout/plugins/general/smsCommunicator/activities/SmsCommunicatorOtpActivity.kt b/plugins/main/src/main/java/info/nightscout/plugins/general/smsCommunicator/activities/SmsCommunicatorOtpActivity.kt index 18e444505a..f5d10cc7b4 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/general/smsCommunicator/activities/SmsCommunicatorOtpActivity.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/general/smsCommunicator/activities/SmsCommunicatorOtpActivity.kt @@ -12,7 +12,7 @@ import android.view.View import android.view.WindowManager import com.google.common.primitives.Ints.min import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel -import dagger.android.support.DaggerAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.ui.dialogs.OKDialog import info.nightscout.core.ui.toast.ToastUtils import info.nightscout.core.utils.fabric.FabricPrivacy @@ -28,7 +28,7 @@ import info.nightscout.shared.interfaces.ResourceHelper import net.glxn.qrgen.android.QRCode import javax.inject.Inject -class SmsCommunicatorOtpActivity : DaggerAppCompatActivity() { +class SmsCommunicatorOtpActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var smsCommunicator: SmsCommunicator diff --git a/plugins/main/src/main/java/info/nightscout/plugins/general/wear/wearintegration/DataHandlerMobile.kt b/plugins/main/src/main/java/info/nightscout/plugins/general/wear/wearintegration/DataHandlerMobile.kt index 0bcd489faf..066e49bf2b 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/general/wear/wearintegration/DataHandlerMobile.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/general/wear/wearintegration/DataHandlerMobile.kt @@ -19,6 +19,7 @@ import info.nightscout.database.ValueWrapper import info.nightscout.database.entities.Bolus import info.nightscout.database.entities.BolusCalculatorResult import info.nightscout.database.entities.GlucoseValue +import info.nightscout.database.entities.HeartRate import info.nightscout.database.entities.TemporaryBasal import info.nightscout.database.entities.TemporaryTarget import info.nightscout.database.entities.TotalDailyDose @@ -28,6 +29,7 @@ import info.nightscout.database.entities.interfaces.end import info.nightscout.database.impl.AppRepository import info.nightscout.database.impl.transactions.CancelCurrentTemporaryTargetIfAnyTransaction import info.nightscout.database.impl.transactions.InsertAndCancelCurrentTemporaryTargetTransaction +import info.nightscout.database.impl.transactions.InsertOrUpdateHeartRateTransaction import info.nightscout.interfaces.Config import info.nightscout.interfaces.Constants import info.nightscout.interfaces.GlucoseUnit @@ -308,6 +310,10 @@ class DataHandlerMobile @Inject constructor( aapsLogger.debug(LTag.WEAR, "WearException received $it from ${it.sourceNodeId}") fabricPrivacy.logWearException(it) }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventData.ActionHeartRate::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ handleHeartRate(it) }, fabricPrivacy::logException) } private fun handleTddStatus() { @@ -1230,4 +1236,15 @@ class DataHandlerMobile @Inject constructor( @Synchronized private fun sendError(errorMessage: String) { rxBus.send(EventMobileToWear(EventData.ConfirmAction(rh.gs(info.nightscout.core.ui.R.string.error), errorMessage, returnCommand = EventData.Error(dateUtil.now())))) // ignore return path } + + /** Stores heart rate events coming from the Wear device. */ + private fun handleHeartRate(actionHeartRate: EventData.ActionHeartRate) { + aapsLogger.debug(LTag.WEAR, "Heart rate received $actionHeartRate from ${actionHeartRate.sourceNodeId}") + val hr = HeartRate( + duration = actionHeartRate.duration, + timestamp = actionHeartRate.timestamp, + beatsPerMinute = actionHeartRate.beatsPerMinute, + device = actionHeartRate.device) + repository.runTransaction(InsertOrUpdateHeartRateTransaction(hr)).blockingAwait() + } } diff --git a/plugins/main/src/main/java/info/nightscout/plugins/profile/ProfilePlugin.kt b/plugins/main/src/main/java/info/nightscout/plugins/profile/ProfilePlugin.kt index 2c50a283d2..22129c0409 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/profile/ProfilePlugin.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/profile/ProfilePlugin.kt @@ -255,6 +255,7 @@ class ProfilePlugin @Inject constructor( isEdited = false createAndStoreConvertedProfile() aapsLogger.debug(LTag.PROFILE, "Accepted ${profiles.size} profiles") + storeSettings() rxBus.send(EventLocalProfileChanged()) } else aapsLogger.debug(LTag.PROFILE, "ProfileStore not accepted") diff --git a/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinClassic.kt b/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinClassic.kt index 34b3fa3ec8..66c5e9277a 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinClassic.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinClassic.kt @@ -1,6 +1,5 @@ package info.nightscout.plugins.skins -import android.util.DisplayMetrics import info.nightscout.interfaces.Config import info.nightscout.plugins.R import info.nightscout.plugins.databinding.OverviewFragmentBinding @@ -14,8 +13,8 @@ class SkinClassic @Inject constructor(private val config: Config) : SkinInterfac override val mainGraphHeight: Int get() = 200 override val secondaryGraphHeight: Int get() = 100 - override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { - super.preProcessLandscapeOverviewLayout(dm, binding, isLandscape, isTablet, isSmallHeight) + override fun preProcessLandscapeOverviewLayout(binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { + super.preProcessLandscapeOverviewLayout(binding, isLandscape, isTablet, isSmallHeight) if (!config.NSCLIENT && (isSmallHeight || isLandscape)) moveButtonsLayout(binding.root) } } diff --git a/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinInterface.kt b/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinInterface.kt index 327c3f5de5..8c00941204 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinInterface.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinInterface.kt @@ -1,6 +1,5 @@ package info.nightscout.plugins.skins -import android.util.DisplayMetrics import android.util.TypedValue.COMPLEX_UNIT_PX import android.view.View import android.widget.LinearLayout @@ -18,16 +17,11 @@ interface SkinInterface { val secondaryGraphHeight: Int // in dp // no pre processing by default - fun preProcessLandscapeActionsLayout(dm: DisplayMetrics, binding: ActionsFragmentBinding) { + fun preProcessLandscapeActionsLayout(isLandscape: Boolean, binding: ActionsFragmentBinding) { } - fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { - // pre-process landscape mode - val screenWidth = dm.widthPixels - val screenHeight = dm.heightPixels - val landscape = screenHeight < screenWidth - - if (landscape) { + fun preProcessLandscapeOverviewLayout(binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { + if (isLandscape) { val iobLayout = binding.infoLayout.iobLayout val iobLayoutParams = iobLayout.layoutParams as ConstraintLayout.LayoutParams val timeLayout = binding.infoLayout.timeLayout @@ -59,7 +53,7 @@ interface SkinInterface { for (v in texts) v.setTextSize(COMPLEX_UNIT_PX, v.textSize * 1.3f) } binding.statusLightsLayout.apply { - val texts = listOf(cannulaAge, insulinAge, reservoirLevel, sensorAge, pbAge, batteryLevel) + val texts = listOf(cannulaAge, insulinAge, reservoirLevel, sensorAge, pbAge, pbLevel) for (v in texts) v.setTextSize(COMPLEX_UNIT_PX, v.textSize * 1.3f) } timeLayout.orientation = LinearLayout.HORIZONTAL diff --git a/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinLargeDisplay.kt b/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinLargeDisplay.kt index 3ef880d299..69497bf3dd 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinLargeDisplay.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinLargeDisplay.kt @@ -1,6 +1,5 @@ package info.nightscout.plugins.skins -import android.util.DisplayMetrics import info.nightscout.interfaces.Config import info.nightscout.plugins.R import info.nightscout.plugins.databinding.OverviewFragmentBinding @@ -14,8 +13,8 @@ class SkinLargeDisplay @Inject constructor(private val config: Config) : SkinInt override val mainGraphHeight: Int get() = 400 override val secondaryGraphHeight: Int get() = 150 - override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { - super.preProcessLandscapeOverviewLayout(dm, binding, isLandscape, isTablet, isSmallHeight) + override fun preProcessLandscapeOverviewLayout(binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { + super.preProcessLandscapeOverviewLayout(binding, isLandscape, isTablet, isSmallHeight) if (!config.NSCLIENT && (isSmallHeight || isLandscape)) moveButtonsLayout(binding.root) } } diff --git a/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinLowRes.kt b/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinLowRes.kt index 4af731facf..f09f389af6 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinLowRes.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/skins/SkinLowRes.kt @@ -1,6 +1,5 @@ package info.nightscout.plugins.skins -import android.util.DisplayMetrics import android.view.View.GONE import android.view.ViewGroup import info.nightscout.interfaces.Config @@ -17,27 +16,26 @@ class SkinLowRes @Inject constructor(private val config: Config) : SkinInterface override val mainGraphHeight: Int get() = 200 override val secondaryGraphHeight: Int get() = 100 - override fun preProcessLandscapeActionsLayout(dm: DisplayMetrics, binding: ActionsFragmentBinding) { - val screenWidth = dm.widthPixels - val screenHeight = dm.heightPixels - val isLandscape = screenHeight < screenWidth - + override fun preProcessLandscapeActionsLayout(isLandscape: Boolean, binding: ActionsFragmentBinding) { if (!isLandscape) { binding.status.apply { - sensorAgeLabel.visibility = GONE + sensorLabel.text = "" sensorAgeLabel.visibility = GONE sensorLevelLabel.visibility = GONE insulinAgeLabel.visibility = GONE + insulinLabel.text = "" insulinLevelLabel.visibility = GONE + cannulaOrPatch.text = "" cannulaAgeLabel.visibility = GONE cannulaUsageLabel.visibility = GONE + pbLabel.text = "" pbAgeLabel.visibility = GONE pbLevelLabel.visibility = GONE } } } - override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { + override fun preProcessLandscapeOverviewLayout(binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { if (!config.NSCLIENT && isLandscape) moveButtonsLayout(binding.root) binding.apply { diff --git a/plugins/main/src/main/res/layout/actions_stats_fragment.xml b/plugins/main/src/main/res/layout/actions_stats_fragment.xml index 01cd378ed7..92369d4d14 100644 --- a/plugins/main/src/main/res/layout/actions_stats_fragment.xml +++ b/plugins/main/src/main/res/layout/actions_stats_fragment.xml @@ -12,6 +12,7 @@ android:focusable="true"> + app:justifyContent="space_around"> - + app:alignContent="stretch" + app:alignItems="stretch" + app:flexDirection="row" + app:flexWrap="wrap" + app:justifyContent="space_around"> + android:gravity="center"> @@ -73,18 +70,15 @@ + android:gravity="center"> - + diff --git a/plugins/main/src/main/res/values-bg-rBG/strings.xml b/plugins/main/src/main/res/values-bg-rBG/strings.xml index c2b2f04624..1d61927b2f 100644 --- a/plugins/main/src/main/res/values-bg-rBG/strings.xml +++ b/plugins/main/src/main/res/values-bg-rBG/strings.xml @@ -141,7 +141,6 @@ Грешка в базалните стойности Грешка в стойностите за цел Грешка в стойностите за чувствителност (ISF) - Името на профила съдържа точка.\nТова не се поддържа от НС.\nПрофилът не е качен в НС. Невалиден профил %1$s не приет от NS Изглед Грешки diff --git a/plugins/main/src/main/res/values-ca-rES/strings.xml b/plugins/main/src/main/res/values-ca-rES/strings.xml index 178137210a..9a686611c5 100644 --- a/plugins/main/src/main/res/values-ca-rES/strings.xml +++ b/plugins/main/src/main/res/values-ca-rES/strings.xml @@ -128,7 +128,6 @@ Error en els valors de basal Error en els valors de l\'objectiu Error en els valors de ISF - El nom del perfil conté punts.\nAixò NS no ho permet.\nPerfil no enviat a NS. Perfil invàlid %1$s no acceptat de NS Vista Errors diff --git a/plugins/main/src/main/res/values-cs-rCZ/strings.xml b/plugins/main/src/main/res/values-cs-rCZ/strings.xml index b68c6e54c0..aaf6688de9 100644 --- a/plugins/main/src/main/res/values-cs-rCZ/strings.xml +++ b/plugins/main/src/main/res/values-cs-rCZ/strings.xml @@ -141,7 +141,7 @@ Chyba v hodnotách bazálu Chyba v hodnotách cílové gl. Chyba v hodnotách inz. citlivosti - Název profilu obsahuje tečky.\nToto není v NS podporováno.\nProfil není přenesen do NS. + Některý název profilu obsahuje tečky.\nToto není v NS podporováno.\nProfily nebudou přeneseny do NS. Neplatný profil %1$s nebyl přijat z NS Zobrazit Chyby diff --git a/plugins/main/src/main/res/values-da-rDK/strings.xml b/plugins/main/src/main/res/values-da-rDK/strings.xml index 515e6f75ee..2d87b05019 100644 --- a/plugins/main/src/main/res/values-da-rDK/strings.xml +++ b/plugins/main/src/main/res/values-da-rDK/strings.xml @@ -137,7 +137,6 @@ Fejl i basal rate værdier Fejl i målværdier Fejl i ISF-værdier - Profilnavn indeholder punktum.\nDette understøttes ikke af NS.\nProfilen er ikke uploadet til NS. Ugyldig profil %1$s blev ikke accepteret fra NS Vis Fejl diff --git a/plugins/main/src/main/res/values-de-rDE/strings.xml b/plugins/main/src/main/res/values-de-rDE/strings.xml index 8561650af8..747e6bbcbf 100644 --- a/plugins/main/src/main/res/values-de-rDE/strings.xml +++ b/plugins/main/src/main/res/values-de-rDE/strings.xml @@ -141,7 +141,6 @@ Fehler in den Basalwerten Fehler in den Zielwerten Fehler in ISF-Werten - Profilname enthält Punkte.\nDies wird von NS nicht unterstützt.\nProfil wird nicht zu NS hochgeladen. Ungültiges Profil %1$s wurde von NS nicht akzeptiert Ansicht Fehler diff --git a/plugins/main/src/main/res/values-es-rES/strings.xml b/plugins/main/src/main/res/values-es-rES/strings.xml index 929f97470a..b96b3ba3d1 100644 --- a/plugins/main/src/main/res/values-es-rES/strings.xml +++ b/plugins/main/src/main/res/values-es-rES/strings.xml @@ -141,7 +141,7 @@ Error en valores basales Error en los valores de objetivo Error en valores ISF - El nombre de perfil contiene puntos.\nEsto no está permitido por NS.\nEl perfil no se cargará en NS. + Algunos nombres de perfiles contienen puntos.\nEsto no está soportado por NS.\nLos perfiles no serán subidos a NS. Perfil no válido %1$s no aceptado desde Nightscout Vista Errores diff --git a/plugins/main/src/main/res/values-fr-rFR/strings.xml b/plugins/main/src/main/res/values-fr-rFR/strings.xml index dc8dbeda85..a72e2732f1 100644 --- a/plugins/main/src/main/res/values-fr-rFR/strings.xml +++ b/plugins/main/src/main/res/values-fr-rFR/strings.xml @@ -141,7 +141,7 @@ Erreur dans les valeurs de basal Erreur dans les valeurs cibles Erreur dans les valeurs de SI - Le nom du profil contient des points.\nCe n\'est pas pris en charge par NS.\nLe profil n\'est pas remonté dans NS. + Certains noms de profil contiennent des points.\nCeci n\'est pas supporté par NS.\nLes profils ne seront pas téléchargés vers NS. Profil %1$s non valide, refusé par NS Vue Erreurs diff --git a/plugins/main/src/main/res/values-hr-rHR/strings.xml b/plugins/main/src/main/res/values-hr-rHR/strings.xml index ebe4473f24..dbefb7ae60 100644 --- a/plugins/main/src/main/res/values-hr-rHR/strings.xml +++ b/plugins/main/src/main/res/values-hr-rHR/strings.xml @@ -94,7 +94,6 @@ Pogreška u bazalnim vrijednostima Pogreška u ciljanim vrijednostima Pogreška u ISF vrijednostima - Naziv profila sadrži točke.\nNS to ne podržava.\nProfil nije učitan u NS. Nevažeći profil %1$s nije prihvaćen od NS-a Pogled Greške diff --git a/plugins/main/src/main/res/values-it-rIT/strings.xml b/plugins/main/src/main/res/values-it-rIT/strings.xml index ed08f12687..ef9b1df5df 100644 --- a/plugins/main/src/main/res/values-it-rIT/strings.xml +++ b/plugins/main/src/main/res/values-it-rIT/strings.xml @@ -88,6 +88,8 @@ Corpo del messaggio non valido Invia SMS se si verifica l\'evento \"micro irraggiungibile\" Segnala micro irraggiungibile + Per cambiare la modalità loop a LGS (Sospensione a glicemia bassa) rispondi con il codice %1$s + Per cambiare la modalità loop a Loop Chiuso rispondi con il codice %1$s Modalità loop corrente %1$s Formato errato BG: @@ -139,7 +141,7 @@ Errore nei valori della basale Errore nei valori del target Errore nei valori ISF - Il nome profilo contiene dei punti.\nQuesto non è supportato da NS.\nIl profilo non viene caricato in NS. + Alcuni nomi profilo contengono punti.\nQuesto non è supportato da NS.\nI profili non verranno caricati su NS. Profilo %1$s non valido - non accettato da NS Vista Errori @@ -317,8 +319,11 @@ Mostra una notifica persistente con una breve panoramica di ciò che sta facendo il tuo loop DATI VECCHI tentativo di recupero dati dal micro. + TDD: Dati ancora vecchi! Non è possibile caricare dal micro. g h + Nessun cambio profilo attivo! + Profilo:\n\nTimeshift: %1$\nPercentuale: %2$d%%\" %1$.2fU %1$.0f%% Nessun profilo caricato Si applica solo in modalità APS! @@ -330,6 +335,7 @@ Ultima esecuzione Ultima attivazione Oggi + ponderato I target si applicano solo in modalità APS! Nessun dato storico! U @@ -337,4 +343,5 @@ fino a RANGE PREDEFINITO target + Velocità: %1$.2fU/h (%2$.2f%%) \nDurata %3$d min diff --git a/plugins/main/src/main/res/values-iw-rIL/strings.xml b/plugins/main/src/main/res/values-iw-rIL/strings.xml index d7dde30687..effb274f07 100644 --- a/plugins/main/src/main/res/values-iw-rIL/strings.xml +++ b/plugins/main/src/main/res/values-iw-rIL/strings.xml @@ -141,7 +141,6 @@ שגיאה בערכי המינון הבזאלי שגיאה בערכי המטרה שגיאה בערכי יחס התיקון - שם הפרופיל מכיל נקודות.\nשם כזה אינו נתמך ע\"י Nightscout \n הפרופיל לא הועלה ל-Nightscout. הפרופיל הבלתי חוקי %1$s מ-Nightscout לא מאושר תצוגה שגיאות diff --git a/plugins/main/src/main/res/values-ko-rKR/strings.xml b/plugins/main/src/main/res/values-ko-rKR/strings.xml index 4a29c8cd2a..48849f8317 100644 --- a/plugins/main/src/main/res/values-ko-rKR/strings.xml +++ b/plugins/main/src/main/res/values-ko-rKR/strings.xml @@ -123,7 +123,6 @@ 우선 현재 변경사항을을 저장하거나 재설정하세요 현재 프로파일을 삭제 하시겠습니까? 단위: - 프로파일명에 점을 포함하고 있습니다.\n이는 NS에서 지원하지 않습니다.\n프로파일이 NS에 업로드되지 않습니다. 에러 프로파일명: 현재 프로파일을 삭제 하시겠습니까 diff --git a/plugins/main/src/main/res/values-lt-rLT/strings.xml b/plugins/main/src/main/res/values-lt-rLT/strings.xml index 2477a9b96d..2cb858511c 100644 --- a/plugins/main/src/main/res/values-lt-rLT/strings.xml +++ b/plugins/main/src/main/res/values-lt-rLT/strings.xml @@ -100,15 +100,15 @@ Valandinė bazė: prieš %1$dmin Sustabdyta (%1$d m) - Būsenos nuskaityti nepavyko + Statuso nuskaityti nepavyko Profilio perjungimas sukurtas Laikinos bazės trukmė turi būti kartotinė %1$d minučių ir didesnė nei 0. QR kodas vienkartinio slaptažodžio nustatymui Rodyti AIO detaliai Rodyti laikrodyje bazės ir bolusų AIO Ciklas išjungtas - Rodyti KG - Pridėti KG prie statuso linijos + Rodyti GĮ + Prie statuso linijos pridėti glikemijos įtaką Maistas Rodyti maisto ruošinius iš Nightscout @@ -130,7 +130,7 @@ Profilis VP - Nustatykite vietinį profilį. + Nustatyti vietinį profilį. pridėti naują į sąrašą Ar norite pakeisti profilį ir atsisakyti pakeitimų, atliktų dabartiniame profilyje? Pirmiausia išsaugoti arba anuliuoti dabartinius pokyčius @@ -141,7 +141,7 @@ Bazės reikšmių klaida Tikslinės glikemijos reikšmės klaida JIF reikšmės klaida - Profilio pavadinime yra taškų.\nŠios funkcijos NS nepalaiko.\nProfilis neįkeltas į NS. + Profilio pavadinime yra taškų.\nŠios funkcijos NS nepalaiko.\nProfilis nebus įkeltas į NS. Neteisingas profilis %1$s nepriimtas iš NS Rodymas Klaidos @@ -169,25 +169,25 @@ Pompos baterija Kaniulė Ištęsto boluso funkcija sustabdys uždaro ciklo režimą ištęsto boluso veikimo metu. Ar tikrai norite patvirtinti savo pasirinkimą? - kateterio amžius + kateteris Patch pompos laikas Patch pompa Pradžios ekrane rodyti spalvotus indikatorius - Kaniulės ribinio laiko įspėjimas [h] - Kaniulės kritinio laiko įspėjimas [h] - Insulino ribinio laiko įspėjimas [h] - Insulino kritinio laiko įspėjimas [h] - Jutiklio ribinio laiko įspėjimas [h] - Jutiklio kritinio laiko įspėjimas [h] - Įspėjimo apie žemą sensoriaus baterijos lygį riba [%] - Įspėjimo apie kritiškai žemą sensoriaus baterijos lygį riba [%] - Pompos baterijos ribinio laiko įspėjimas [h] - Pompos baterijos kritinio laiko įspėjimas [h] - Įspėjimo apie žemą rezervuaro lygį riba [U] - Įspėjimo apie kritiškai žemą rezervuaro lygį riba [U] - Pompos baterijos ribinio įkrovimo lygio įspėjimas [%] - Pompos baterijos kritinio įkrovimo lygio įspėjimas [%] + Perspėjimo apie kateterio naudojimo laiką slenkstis [h] + Perspėjimo apie kritinį kateterio naudojimo laiką slenkstis [h] + Perspėjimo apie insulino naudojimo laiką slenkstis [h] + Perspėjimo apie kritinį insulino naudojimo laiką slenkstis [h] + Perspėjimo apie sensoriaus naudojimo laiką slenkstis [h] + Perspėjimo apie kritinį sensoriaus naudojimo laiką slenkstis [h] + Perspėjimo apie senkančią sensoriaus bateriją slenkstis [%] + Perspėjimo apie kritiškai senkančią sensoriaus bateriją slenkstis [%] + Perspėjimo apie pompos baterijos naudojimo laiką slenkstis [h] + Perspėjimo apie kritinį pompos baterijos naudojimo laiką slenkstis [h] + Perspėjimo apie senkantį rezervuarą slenkstis [U] + Perspėjimo apie kritiškai senkantį rezervuarą slenkstis [U] + Perspėjimo apie senkančią pompos bateriją slenkstis [%] + Perspėjimo apie kritiškai senkančią pompos bateriją slenkstis [%] Kopijuoti nustatymus iš NS grafikas insulinas @@ -206,12 +206,12 @@ Dexcom app neįdiegta. Atnaujinkite savo Dexcom programėlės versiją Nepavyko paleisti NGJ programos. Įsitikinkite, kad ji įdiegta. - Negalimas + Nėra duomenų Apribojimų pažeidimas Pakeiskite įvestus duomenis! AtviraDKS Siuntėjas - KG duomenų būklė + KG duomenų statusas Greitojo patarėjo nustatymai Laikyti ekraną įjungtą Neleidžia Android sistemai išjungti ekrano. Naudojama daug energijos, jei telefonas neprijungtas prie maitinimo šaltinio. @@ -287,7 +287,7 @@ Stebėti ir kontroliuoti AndroidAPS naudojant WearOS laikrodį. (nėra prijungto laikrodžio) Pompos statusas - Ciklo būklė + Ciklo statusas Wizard asistento skaičiuotuvas:\nInsulinas: %1$.2fU\nAngliavandeniai: %2$dg Pasirinktas greitasis patarėjas nebepasiekiamas, atnaujinkite valdiklį QuickWizard asistentas: %1$s\nInsulinas: %2$.2fU\nAngliavandeniai: %3$dg diff --git a/plugins/main/src/main/res/values-nl-rNL/strings.xml b/plugins/main/src/main/res/values-nl-rNL/strings.xml index 5b24ea7da7..24af307935 100644 --- a/plugins/main/src/main/res/values-nl-rNL/strings.xml +++ b/plugins/main/src/main/res/values-nl-rNL/strings.xml @@ -141,7 +141,6 @@ Fout in basaal waarden Fout in streefdoel Fout in ISF waarden - Profielnaam bevat punten.\nDit wordt niet ondersteund door NS.\nProfiel is niet geüpload naar NS. Ongeldig profiel %1$s niet geaccepteerd door NS Weergeven Foutmeldingen diff --git a/plugins/main/src/main/res/values-no-rNO/strings.xml b/plugins/main/src/main/res/values-no-rNO/strings.xml index fce278fd07..0e65db557e 100644 --- a/plugins/main/src/main/res/values-no-rNO/strings.xml +++ b/plugins/main/src/main/res/values-no-rNO/strings.xml @@ -141,7 +141,7 @@ Feil i basalverdiene Feil i BS målverdier Feil i IF verdien - Profilnavnet inneholder prikker.\nDette støttes ikke av NS.\nProfilen er ikke lastet opp til NS. + Noen av profilnavnene inneholder punktum.\nDette støttes ikke av NS.\nProfilen er ikke lastet opp til NS. Ugyldig profil %1$s ikke akseptert fra NS Visning Feil diff --git a/plugins/main/src/main/res/values-pl-rPL/strings.xml b/plugins/main/src/main/res/values-pl-rPL/strings.xml index fd230d24b9..5d4e75155a 100644 --- a/plugins/main/src/main/res/values-pl-rPL/strings.xml +++ b/plugins/main/src/main/res/values-pl-rPL/strings.xml @@ -141,7 +141,7 @@ Błąd w wartościach bazy Błąd w wartościach docelowych Błąd w wartościach ISF - Nazwa profilu zawiera kropki.\nTo nie jest obsługiwane przez NS.\nProfil nie zostanie przesyłany do NS. + Nazwa któregoś z profili zawiera kropki.\nTo nie jest obsługiwane przez NS.\nProfile nie zostaną przesłane do NS. Błędny profil %1$s nie został zaakceptowany z NS Podgląd Błędy diff --git a/plugins/main/src/main/res/values-pt-rBR/strings.xml b/plugins/main/src/main/res/values-pt-rBR/strings.xml index f9d8ff1342..301140ee78 100644 --- a/plugins/main/src/main/res/values-pt-rBR/strings.xml +++ b/plugins/main/src/main/res/values-pt-rBR/strings.xml @@ -138,7 +138,6 @@ Erro nos valores da basal Erro nos valores de alvo Erro nos valores de FSI - Nome do perfil contém pontos.\nIsso não é suportado pelo NS.\nPerfil não foi enviado para o NS. Perfil inválido %1$s não aceito do NS Visualização Erros diff --git a/plugins/main/src/main/res/values-pt-rPT/strings.xml b/plugins/main/src/main/res/values-pt-rPT/strings.xml index f21099c6e5..00b63b9b43 100644 --- a/plugins/main/src/main/res/values-pt-rPT/strings.xml +++ b/plugins/main/src/main/res/values-pt-rPT/strings.xml @@ -128,7 +128,6 @@ Erro nos valores da basal Erros no valor alvo Erro nos valores do FSI - Nome do perfil contém pontos.\nIsso não é suportado pelo NS.\nPerfil não é enviado para o NS. Perfil inválido %1$s não aceite do NS Ver Erros diff --git a/plugins/main/src/main/res/values-ro-rRO/strings.xml b/plugins/main/src/main/res/values-ro-rRO/strings.xml index f17279dde0..0fa037ed08 100644 --- a/plugins/main/src/main/res/values-ro-rRO/strings.xml +++ b/plugins/main/src/main/res/values-ro-rRO/strings.xml @@ -129,7 +129,6 @@ Eroare in valorile ratei bazale Eroare in valorile tinta Eroare in valorile ISF - Numele profilului conține puncte.\nAcest lucru nu este permis de NS.\nProfilul nu a fost înregistrat în NS. Profilul invalid din NS %1$s nu este acceptat Vizualizare Erori diff --git a/plugins/main/src/main/res/values-ru-rRU/strings.xml b/plugins/main/src/main/res/values-ru-rRU/strings.xml index e54e91a7cb..162acd588c 100644 --- a/plugins/main/src/main/res/values-ru-rRU/strings.xml +++ b/plugins/main/src/main/res/values-ru-rRU/strings.xml @@ -141,7 +141,7 @@ Ошибка в величине базала Ошибка в целевых значениях Ошибка в значении фактора чувствительности к инсулину ISF - Имя профиля содержит точки.\nЭто не поддерживается NS.\nПрофиль не выгружен в NS. + В некоторых наименованиях профиля содержатся точки.\nЭто не поддерживается NS.\nПрофиль не выгружен в NS. Неверный профиль %1$s не принят из NS Смотреть Ошибки diff --git a/plugins/main/src/main/res/values-sk-rSK/strings.xml b/plugins/main/src/main/res/values-sk-rSK/strings.xml index 4dbd821b3e..c9ecd09c48 100644 --- a/plugins/main/src/main/res/values-sk-rSK/strings.xml +++ b/plugins/main/src/main/res/values-sk-rSK/strings.xml @@ -141,7 +141,7 @@ Chyba v hodnotách bazálu Chyba v cieľových hodnotách Chyba v hodnotách citlivosti - Názov profilu obsahuje bodky.\nToto nie je v NS podporované.\nProfil nie je prenesený do NS. + Názov profilu obsahuje bodky.\nToto nie je v NS podporované.\nProfil nebude prenesený do NS. Neplatný profil %1$s nebol akceptovaný z NS Zobraziť Chyby diff --git a/plugins/main/src/main/res/values-sv-rSE/strings.xml b/plugins/main/src/main/res/values-sv-rSE/strings.xml index c3da5fd073..5240f3e19a 100644 --- a/plugins/main/src/main/res/values-sv-rSE/strings.xml +++ b/plugins/main/src/main/res/values-sv-rSE/strings.xml @@ -129,7 +129,6 @@ Fel i basaldoser Fel i målvärden Fel i korrektionskvoter - Profilnamnet innehåller punkter.\nDetta stöds inte av NS.\nProfil överförs inte till NS. Ogiltig profil %1$s från NS accepteras inte Visa Fel diff --git a/plugins/main/src/main/res/values-tr-rTR/strings.xml b/plugins/main/src/main/res/values-tr-rTR/strings.xml index dd10fad627..e8d9613ec9 100644 --- a/plugins/main/src/main/res/values-tr-rTR/strings.xml +++ b/plugins/main/src/main/res/values-tr-rTR/strings.xml @@ -141,7 +141,7 @@ Bazal değerlerde hata Hedef değerlerde hata ISF değerinde hata - Profil adı noktalar içeriyor.\nBu NS tarafından desteklenmiyor.\n Profil NS\'a yüklenmez. + Profil adlarından bazıları noktalar içeriyor.\nBu, NS tarafından desteklenmiyor.\nProfiller NS\'ye yüklenmeyecek. Geçersiz profil %1$s NS tarafından kabul edilmiyor Görünüm Hatalar diff --git a/plugins/main/src/main/res/values-zh-rCN/strings.xml b/plugins/main/src/main/res/values-zh-rCN/strings.xml index 24e6a2a9c5..9b56120c78 100644 --- a/plugins/main/src/main/res/values-zh-rCN/strings.xml +++ b/plugins/main/src/main/res/values-zh-rCN/strings.xml @@ -135,7 +135,6 @@ 基础率数值错误 目标数值错误 ISF数值错误 - 配置文件名称包含点。\n这不受 NS 支持。\n配置未上载到 NS。 不接受来自NS的无效配置文件%1$s 查看 错误 diff --git a/plugins/main/src/main/res/values/strings.xml b/plugins/main/src/main/res/values/strings.xml index 833812f12c..df582c2e6f 100644 --- a/plugins/main/src/main/res/values/strings.xml +++ b/plugins/main/src/main/res/values/strings.xml @@ -156,7 +156,7 @@ Error in basal values Error in target values Error in ISF values - Profile name contains dots.\nThis is not supported by NS.\nProfile is not uploaded to NS. + Some of profile names contains dots.\nThis is not supported by NS.\nProfiles will not be uploaded to NS. Invalid profile %1$s not accepted from NS View Errors @@ -291,6 +291,7 @@ Predictions Treatments + Heart Rate Deviation slope Activity Blood Glucose Impact @@ -308,6 +309,7 @@ ABS DEVSLOPE TREAT + HR SENS Graph scale Graph diff --git a/plugins/source/src/main/java/info/nightscout/source/activities/RequestDexcomPermissionActivity.kt b/plugins/source/src/main/java/info/nightscout/source/activities/RequestDexcomPermissionActivity.kt index 142601e644..5a6ef6242c 100644 --- a/plugins/source/src/main/java/info/nightscout/source/activities/RequestDexcomPermissionActivity.kt +++ b/plugins/source/src/main/java/info/nightscout/source/activities/RequestDexcomPermissionActivity.kt @@ -1,10 +1,10 @@ package info.nightscout.source.activities import android.os.Bundle -import info.nightscout.core.ui.activities.DialogAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.source.DexcomPlugin -class RequestDexcomPermissionActivity : DialogAppCompatActivity() { +class RequestDexcomPermissionActivity : TranslatedDaggerAppCompatActivity() { private val requestCode = "AndroidAPS <3".map { it.code }.sum() diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsShared/NSClientFragment.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsShared/NSClientFragment.kt index 1feb719779..1960ffb2b8 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsShared/NSClientFragment.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsShared/NSClientFragment.kt @@ -11,6 +11,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.text.toSpanned +import androidx.core.view.MenuCompat import androidx.core.view.MenuProvider import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.LinearLayoutManager @@ -113,7 +114,7 @@ class NSClientFragment : DaggerFragment(), MenuProvider, PluginFragment { menu.add(Menu.FIRST, ID_MENU_RESTART, 0, rh.gs(R.string.restart)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) menu.add(Menu.FIRST, ID_MENU_SEND_NOW, 0, rh.gs(R.string.deliver_now)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) menu.add(Menu.FIRST, ID_MENU_FULL_SYNC, 0, rh.gs(R.string.full_sync)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) - menu.setGroupDividerEnabled(true) + MenuCompat.setGroupDividerEnabled(menu, true) } override fun onMenuItemSelected(item: MenuItem): Boolean = diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsShared/StoreDataForDbImpl.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsShared/StoreDataForDbImpl.kt index e3e340c09a..f7fa42799c 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsShared/StoreDataForDbImpl.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsShared/StoreDataForDbImpl.kt @@ -18,6 +18,17 @@ import info.nightscout.database.entities.UserEntry import info.nightscout.database.entities.ValueWithUnit import info.nightscout.database.impl.AppRepository import info.nightscout.database.impl.transactions.CgmSourceTransaction +import info.nightscout.database.impl.transactions.InvalidateBolusCalculatorResultTransaction +import info.nightscout.database.impl.transactions.InvalidateBolusTransaction +import info.nightscout.database.impl.transactions.InvalidateCarbsTransaction +import info.nightscout.database.impl.transactions.InvalidateEffectiveProfileSwitchTransaction +import info.nightscout.database.impl.transactions.InvalidateExtendedBolusTransaction +import info.nightscout.database.impl.transactions.InvalidateGlucoseValueTransaction +import info.nightscout.database.impl.transactions.InvalidateOfflineEventTransaction +import info.nightscout.database.impl.transactions.InvalidateProfileSwitchTransaction +import info.nightscout.database.impl.transactions.InvalidateTemporaryBasalTransaction +import info.nightscout.database.impl.transactions.InvalidateTemporaryTargetTransaction +import info.nightscout.database.impl.transactions.InvalidateTherapyEventTransaction import info.nightscout.database.impl.transactions.SyncNsBolusCalculatorResultTransaction import info.nightscout.database.impl.transactions.SyncNsBolusTransaction import info.nightscout.database.impl.transactions.SyncNsCarbsTransaction @@ -51,6 +62,7 @@ import info.nightscout.interfaces.nsclient.StoreDataForDb import info.nightscout.interfaces.pump.VirtualPump import info.nightscout.interfaces.source.NSClientSource import info.nightscout.interfaces.ui.UiInteraction +import info.nightscout.plugins.sync.R import info.nightscout.rx.bus.RxBus import info.nightscout.rx.events.EventNSClientNewLog import info.nightscout.rx.logging.AAPSLogger @@ -104,6 +116,8 @@ class StoreDataForDbImpl @Inject constructor( override val nsIdDeviceStatuses: MutableList = mutableListOf() override val nsIdFoods: MutableList = mutableListOf() + override val deleteTreatment: MutableList = mutableListOf() + override val deleteGlucoseValue: MutableList = mutableListOf() private val userEntries: MutableList = mutableListOf() private val inserted = HashMap() @@ -961,6 +975,157 @@ class StoreDataForDbImpl @Inject constructor( rxBus.send(EventNSClientNewLog("● DONE NSIDs", "")) } + override fun updateDeletedTreatmentsInDb() { + deleteTreatment.forEach { id -> + if (sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_receive_insulin, false) || config.NSCLIENT) + repository.findBolusByNSId(id)?.let { bolus -> + repository.runTransactionForResult(InvalidateBolusTransaction(bolus.id)) + .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating Bolus", it) } + .blockingGet() + .also { result -> + result.invalidated.forEach { + aapsLogger.debug(LTag.DATABASE, "Invalidated Bolus $it") + invalidated.inc(Bolus::class.java.simpleName) + } + } + } + if (sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_receive_carbs, false) || config.NSCLIENT) + repository.findCarbsByNSId(id)?.let { carb -> + repository.runTransactionForResult(InvalidateCarbsTransaction(carb.id)) + .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating Carbs", it) } + .blockingGet() + .also { result -> + result.invalidated.forEach { + aapsLogger.debug(LTag.DATABASE, "Invalidated Carbs $it") + invalidated.inc(Carbs::class.java.simpleName) + } + } + } + if (sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_receive_temp_target, false) || config.NSCLIENT) + repository.findTemporaryTargetByNSId(id)?.let { gv -> + repository.runTransactionForResult(InvalidateTemporaryTargetTransaction(gv.id)) + .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating TemporaryTarget", it) } + .blockingGet() + .also { result -> + result.invalidated.forEach { + aapsLogger.debug(LTag.DATABASE, "Invalidated TemporaryTarget $it") + invalidated.inc(TemporaryTarget::class.java.simpleName) + } + } + } + if (config.isEngineeringMode() && sp.getBoolean(R.string.key_ns_receive_tbr_eb, false) || config.NSCLIENT) + repository.findTemporaryBasalByNSId(id)?.let { gv -> + repository.runTransactionForResult(InvalidateTemporaryBasalTransaction(gv.id)) + .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating TemporaryBasal", it) } + .blockingGet() + .also { result -> + result.invalidated.forEach { + aapsLogger.debug(LTag.DATABASE, "Invalidated TemporaryBasal $it") + invalidated.inc(TemporaryBasal::class.java.simpleName) + } + } + } + if (sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_receive_profile_switch, false) || config.NSCLIENT) + repository.findEffectiveProfileSwitchByNSId(id)?.let { gv -> + repository.runTransactionForResult(InvalidateEffectiveProfileSwitchTransaction(gv.id)) + .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating EffectiveProfileSwitch", it) } + .blockingGet() + .also { result -> + result.invalidated.forEach { + aapsLogger.debug(LTag.DATABASE, "Invalidated EffectiveProfileSwitch $it") + invalidated.inc(EffectiveProfileSwitch::class.java.simpleName) + } + } + } + if (sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_receive_profile_switch, false) || config.NSCLIENT) + repository.findProfileSwitchByNSId(id)?.let { gv -> + repository.runTransactionForResult(InvalidateProfileSwitchTransaction(gv.id)) + .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating ProfileSwitch", it) } + .blockingGet() + .also { result -> + result.invalidated.forEach { + aapsLogger.debug(LTag.DATABASE, "Invalidated ProfileSwitch $it") + invalidated.inc(ProfileSwitch::class.java.simpleName) + } + } + } + repository.findBolusCalculatorResultByNSId(id)?.let { gv -> + repository.runTransactionForResult(InvalidateBolusCalculatorResultTransaction(gv.id)) + .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating BolusCalculatorResult", it) } + .blockingGet() + .also { result -> + result.invalidated.forEach { + aapsLogger.debug(LTag.DATABASE, "Invalidated BolusCalculatorResult $it") + invalidated.inc(BolusCalculatorResult::class.java.simpleName) + } + } + } + if (sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_receive_therapy_events, false) || config.NSCLIENT) + repository.findTherapyEventByNSId(id)?.let { gv -> + repository.runTransactionForResult(InvalidateTherapyEventTransaction(gv.id)) + .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating TherapyEvent", it) } + .blockingGet() + .also { result -> + result.invalidated.forEach { + aapsLogger.debug(LTag.DATABASE, "Invalidated TherapyEvent $it") + invalidated.inc(TherapyEvent::class.java.simpleName) + } + } + } + if (sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_receive_offline_event, false) && config.isEngineeringMode() || config.NSCLIENT) + repository.findOfflineEventByNSId(id)?.let { gv -> + repository.runTransactionForResult(InvalidateOfflineEventTransaction(gv.id)) + .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating OfflineEvent", it) } + .blockingGet() + .also { result -> + result.invalidated.forEach { + aapsLogger.debug(LTag.DATABASE, "Invalidated OfflineEvent $it") + invalidated.inc(OfflineEvent::class.java.simpleName) + } + } + } + if (config.isEngineeringMode() && sp.getBoolean(R.string.key_ns_receive_tbr_eb, false) || config.NSCLIENT) + repository.findExtendedBolusByNSId(id)?.let { gv -> + repository.runTransactionForResult(InvalidateExtendedBolusTransaction(gv.id)) + .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating ExtendedBolus", it) } + .blockingGet() + .also { result -> + result.invalidated.forEach { + aapsLogger.debug(LTag.DATABASE, "Invalidated ExtendedBolus $it") + invalidated.inc(ExtendedBolus::class.java.simpleName) + } + } + } + } + sendLog("Bolus", Bolus::class.java.simpleName) + sendLog("Carbs", Carbs::class.java.simpleName) + sendLog("TemporaryTarget", TemporaryTarget::class.java.simpleName) + sendLog("TemporaryBasal", TemporaryBasal::class.java.simpleName) + sendLog("EffectiveProfileSwitch", EffectiveProfileSwitch::class.java.simpleName) + sendLog("ProfileSwitch", ProfileSwitch::class.java.simpleName) + sendLog("BolusCalculatorResult", BolusCalculatorResult::class.java.simpleName) + sendLog("TherapyEvent", TherapyEvent::class.java.simpleName) + sendLog("OfflineEvent", OfflineEvent::class.java.simpleName) + sendLog("ExtendedBolus", ExtendedBolus::class.java.simpleName) + } + + override fun updateDeletedGlucoseValuesInDb() { + deleteGlucoseValue.forEach { id -> + repository.findBgReadingByNSId(id)?.let { gv -> + repository.runTransactionForResult(InvalidateGlucoseValueTransaction(gv.id)) + .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating GlucoseValue", it) } + .blockingGet() + .also { result -> + result.invalidated.forEach { + aapsLogger.debug(LTag.DATABASE, "Invalidated GlucoseValue $it") + invalidated.inc(GlucoseValue::class.java.simpleName) + } + } + } + } + sendLog("GlucoseValue", GlucoseValue::class.java.simpleName) + } + private fun sendLog(item: String, clazz: String) { inserted[clazz]?.let { rxBus.send(EventNSClientNewLog("◄ INSERT", "$item $it")) diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/DataSyncSelectorV3Impl.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/DataSyncSelectorV3Impl.kt index 23bdbcb5c0..8c79dfe1b3 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/DataSyncSelectorV3Impl.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/DataSyncSelectorV3Impl.kt @@ -2,6 +2,7 @@ package info.nightscout.plugins.sync.nsclientV3 import info.nightscout.androidaps.annotations.OpenForTesting import info.nightscout.database.impl.AppRepository +import info.nightscout.interfaces.Config import info.nightscout.interfaces.nsclient.StoreDataForDb import info.nightscout.interfaces.plugin.ActivePlugin import info.nightscout.interfaces.profile.ProfileFunction @@ -29,7 +30,8 @@ class DataSyncSelectorV3Impl @Inject constructor( private val activePlugin: ActivePlugin, private val appRepository: AppRepository, private val rxBus: RxBus, - private val storeDataForDb: StoreDataForDb + private val storeDataForDb: StoreDataForDb, + private val config: Config ) : DataSyncSelectorV3 { class QueueCounter( @@ -71,7 +73,7 @@ class DataSyncSelectorV3Impl @Inject constructor( override suspend fun doUpload() { rxBus.send(EventNSClientUpdateGuiStatus()) - if (sp.getBoolean(R.string.key_ns_upload, true) && !isPaused) { + if ((config.NSCLIENT || sp.getBoolean(R.string.key_ns_upload, true)) && !isPaused) { queueCounter.bolusesRemaining = (appRepository.getLastBolusId() ?: 0L) - sp.getLong(R.string.key_ns_bolus_last_synced_id, 0) queueCounter.carbsRemaining = (appRepository.getLastCarbsId() ?: 0L) - sp.getLong(R.string.key_ns_carbs_last_synced_id, 0) queueCounter.bcrRemaining = (appRepository.getLastBolusCalculatorResultId() ?: 0L) - sp.getLong(R.string.key_ns_bolus_calculator_result_last_synced_id, 0) @@ -161,7 +163,7 @@ class DataSyncSelectorV3Impl @Inject constructor( bolus.first.interfaceIDs.nightscoutId != null && bolus.first.id != bolus.second.id -> cont = activePlugin.activeNsClient?.nsUpdate("treatments", DataSyncSelector.PairBolus(bolus.first, bolus.second.id), "$startId/$lastDbId") ?: false } - confirmLastBolusIdIfGreater(bolus.second.id) + if (cont) confirmLastBolusIdIfGreater(bolus.second.id) } ?: run { cont = false } @@ -204,7 +206,7 @@ class DataSyncSelectorV3Impl @Inject constructor( carb.first.interfaceIDs.nightscoutId != null && carb.first.id != carb.second.id -> cont = activePlugin.activeNsClient?.nsUpdate("treatments", DataSyncSelector.PairCarbs(carb.first, carb.second.id), "$startId/$lastDbId") ?: false } - confirmLastCarbsIdIfGreater(carb.second.id) + if (cont) confirmLastCarbsIdIfGreater(carb.second.id) } ?: run { cont = false } @@ -255,7 +257,7 @@ class DataSyncSelectorV3Impl @Inject constructor( "$startId/$lastDbId" ) ?: false } - confirmLastBolusCalculatorResultsIdIfGreater(bolusCalculatorResult.second.id) + if (cont) confirmLastBolusCalculatorResultsIdIfGreater(bolusCalculatorResult.second.id) } ?: run { cont = false } @@ -298,7 +300,7 @@ class DataSyncSelectorV3Impl @Inject constructor( tt.first.interfaceIDs.nightscoutId != null -> cont = activePlugin.activeNsClient?.nsUpdate("treatments", DataSyncSelector.PairTemporaryTarget(tt.first, tt.second.id), "$startId/$lastDbId") ?: false } - confirmLastTempTargetsIdIfGreater(tt.second.id) + if (cont) confirmLastTempTargetsIdIfGreater(tt.second.id) } ?: run { cont = false } @@ -341,7 +343,7 @@ class DataSyncSelectorV3Impl @Inject constructor( food.first.interfaceIDs.nightscoutId != null -> cont = activePlugin.activeNsClient?.nsUpdate("food", DataSyncSelector.PairFood(food.first, food.second.id), "$startId/$lastDbId") ?: false } - confirmLastFoodIdIfGreater(food.second.id) + if (cont) confirmLastFoodIdIfGreater(food.second.id) } ?: run { cont = false } @@ -386,7 +388,7 @@ class DataSyncSelectorV3Impl @Inject constructor( cont = activePlugin.activeNsClient?.nsUpdate("entries", DataSyncSelector.PairGlucoseValue(gv.first, gv.second.id), "$startId/$lastDbId") ?: false } } - confirmLastGlucoseValueIdIfGreater(gv.second.id) + if (cont) confirmLastGlucoseValueIdIfGreater(gv.second.id) } ?: run { cont = false } @@ -429,7 +431,7 @@ class DataSyncSelectorV3Impl @Inject constructor( te.first.interfaceIDs.nightscoutId != null -> cont = activePlugin.activeNsClient?.nsUpdate("treatments", DataSyncSelector.PairTherapyEvent(te.first, te.second.id), "$startId/$lastDbId") ?: false } - confirmLastTherapyEventIdIfGreater(te.second.id) + if (cont) confirmLastTherapyEventIdIfGreater(te.second.id) } ?: run { cont = false } @@ -502,7 +504,7 @@ class DataSyncSelectorV3Impl @Inject constructor( tb.first.interfaceIDs.nightscoutId != null -> cont = activePlugin.activeNsClient?.nsUpdate("treatments", DataSyncSelector.PairTemporaryBasal(tb.first, tb.second.id), "$startId/$lastDbId") ?: false } - confirmLastTemporaryBasalIdIfGreater(tb.second.id) + if (cont) confirmLastTemporaryBasalIdIfGreater(tb.second.id) } ?: run { cont = false } @@ -548,7 +550,7 @@ class DataSyncSelectorV3Impl @Inject constructor( cont = activePlugin.activeNsClient?.nsUpdate("treatments", DataSyncSelector.PairExtendedBolus(eb.first, eb.second.id), "$startId/$lastDbId") ?: false } } else aapsLogger.info(LTag.NSCLIENT, "Ignoring ExtendedBolus. No profile: ${eb.second.id} ") - confirmLastExtendedBolusIdIfGreater(eb.second.id) + if (cont) confirmLastExtendedBolusIdIfGreater(eb.second.id) } ?: run { cont = false } @@ -591,7 +593,7 @@ class DataSyncSelectorV3Impl @Inject constructor( ps.first.interfaceIDs.nightscoutId != null -> cont = activePlugin.activeNsClient?.nsUpdate("treatments", DataSyncSelector.PairProfileSwitch(ps.first, ps.second.id), "$startId/$lastDbId") ?: false } - confirmLastProfileSwitchIdIfGreater(ps.second.id) + if (cont) confirmLastProfileSwitchIdIfGreater(ps.second.id) } ?: run { cont = false } @@ -634,7 +636,7 @@ class DataSyncSelectorV3Impl @Inject constructor( ps.first.interfaceIDs.nightscoutId != null -> cont = activePlugin.activeNsClient?.nsUpdate("treatments", DataSyncSelector.PairEffectiveProfileSwitch(ps.first, ps.second.id), "$startId/$lastDbId") ?: false } - confirmLastEffectiveProfileSwitchIdIfGreater(ps.second.id) + if (cont) confirmLastEffectiveProfileSwitchIdIfGreater(ps.second.id) } ?: run { cont = false } @@ -677,7 +679,7 @@ class DataSyncSelectorV3Impl @Inject constructor( oe.first.interfaceIDs.nightscoutId != null -> cont = activePlugin.activeNsClient?.nsUpdate("treatments", DataSyncSelector.PairOfflineEvent(oe.first, oe.second.id), "$startId/$lastDbId") ?: false } - confirmLastOfflineEventIdIfGreater(oe.second.id) + if (cont) confirmLastOfflineEventIdIfGreater(oe.second.id) } ?: run { cont = false } diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/NSClientV3Plugin.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/NSClientV3Plugin.kt index 19fe7f2266..43b1cd2802 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/NSClientV3Plugin.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/NSClientV3Plugin.kt @@ -381,11 +381,15 @@ class NSClientV3Plugin @Inject constructor( val response = args[0] as JSONObject wsConnected = if (response.optBoolean("success")) { rxBus.send(EventNSClientNewLog("◄ WS", "Subscribed for: ${response.optString("collections")}")) + // during disconnection updated data is not received + // thus run non WS load to get missing data + executeLoop("WS_CONNECT", forceNew = false) true } else { rxBus.send(EventNSClientNewLog("◄ WS", "Auth failed")) false } + rxBus.send(EventNSClientUpdateGuiStatus()) }) } } @@ -417,6 +421,7 @@ class NSClientV3Plugin @Inject constructor( rxBus.send(EventNSClientNewLog("◄ WS", "disconnect storage event")) wsConnected = false initialLoadFinished = false + rxBus.send(EventNSClientUpdateGuiStatus()) } private val onDisconnectAlarm = Emitter.Listener { args -> @@ -461,7 +466,17 @@ class NSClientV3Plugin @Inject constructor( private val onDataDelete = Emitter.Listener { args -> val response = args[0] as JSONObject aapsLogger.debug(LTag.NSCLIENT, "onDataDelete: $response") - rxBus.send(EventNSClientNewLog("◄ WS DELETE", "${response.optString("collection")} ${response.optString("doc")}")) + val collection = response.optString("colName") ?: return@Listener + val identifier = response.optString("identifier") ?: return@Listener + rxBus.send(EventNSClientNewLog("◄ WS DELETE", "$collection $identifier")) + if (collection == "treatments") { + storeDataForDb.deleteTreatment.add(identifier) + storeDataForDb.updateDeletedTreatmentsInDb() + } + if (collection == "entries") { + storeDataForDb.deleteGlucoseValue.add(identifier) + storeDataForDb.updateDeletedGlucoseValuesInDb() + } } private val onAnnouncement = Emitter.Listener { args -> @@ -616,7 +631,7 @@ class NSClientV3Plugin @Inject constructor( 404 -> rxBus.send(EventNSClientNewLog("◄ NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}")) else -> { - rxBus.send(EventNSClientNewLog("◄ ERROR", "ProfileStore")) + rxBus.send(EventNSClientNewLog("◄ ERROR", "${result.errorResponse}")) return true } } @@ -640,7 +655,7 @@ class NSClientV3Plugin @Inject constructor( 404 -> rxBus.send(EventNSClientNewLog("◄ NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}")) else -> { - rxBus.send(EventNSClientNewLog("◄ ERROR", "${dataPair.value.javaClass.simpleName} ")) + rxBus.send(EventNSClientNewLog("◄ ERROR", "${result.errorResponse} ")) return true } } @@ -684,7 +699,7 @@ class NSClientV3Plugin @Inject constructor( 404 -> rxBus.send(EventNSClientNewLog("◄ NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}")) else -> { - rxBus.send(EventNSClientNewLog("◄ ERROR", "${dataPair.value.javaClass.simpleName} ")) + rxBus.send(EventNSClientNewLog("◄ ERROR", "${result.errorResponse} ")) return true } } @@ -729,7 +744,7 @@ class NSClientV3Plugin @Inject constructor( 404 -> rxBus.send(EventNSClientNewLog("◄ NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}")) else -> { - rxBus.send(EventNSClientNewLog("◄ ERROR", "${dataPair.value.javaClass.simpleName} ")) + rxBus.send(EventNSClientNewLog("◄ ERROR", "${result.errorResponse} ")) return true } } @@ -795,7 +810,7 @@ class NSClientV3Plugin @Inject constructor( 404 -> rxBus.send(EventNSClientNewLog("◄ NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}")) else -> { - rxBus.send(EventNSClientNewLog("◄ ERROR", "${dataPair.value.javaClass.simpleName} ")) + rxBus.send(EventNSClientNewLog("◄ ERROR", "${result.errorResponse} ")) return true } } @@ -859,6 +874,7 @@ class NSClientV3Plugin @Inject constructor( slowDown() } } catch (e: Exception) { + rxBus.send(EventNSClientNewLog("◄ ERROR", e.localizedMessage)) aapsLogger.error(LTag.NSCLIENT, "Upload exception", e) return false } diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/workers/DataSyncWorker.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/workers/DataSyncWorker.kt index c9c35d47cd..7b8442fc46 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/workers/DataSyncWorker.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/workers/DataSyncWorker.kt @@ -28,7 +28,10 @@ class DataSyncWorker( dataSyncSelectorV3.doUpload() rxBus.send(EventNSClientNewLog("► UPL", "End")) } else { - rxBus.send(EventNSClientNewLog("► ERROR", "Not connected or write permission")) + if (activePlugin.activeNsClient?.hasWritePermission == true) + rxBus.send(EventNSClientNewLog("► ERROR", "No write permission")) + else if (nsClientV3Plugin.wsConnected) + rxBus.send(EventNSClientNewLog("► ERROR", "Not connected")) // refresh token nsClientV3Plugin.scheduleIrregularExecution(refreshToken = true) } diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/openhumans/ui/OHLoginActivity.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/openhumans/ui/OHLoginActivity.kt index 243200311d..1dc79a3912 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/openhumans/ui/OHLoginActivity.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/openhumans/ui/OHLoginActivity.kt @@ -15,13 +15,13 @@ import androidx.core.view.updatePadding import androidx.core.widget.NestedScrollView import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.button.MaterialButton -import dagger.android.support.DaggerAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity +import info.nightscout.plugins.sync.R import info.nightscout.plugins.sync.di.AuthUrl import info.nightscout.plugins.sync.di.ViewModelFactory -import info.nightscout.plugins.sync.R import javax.inject.Inject -class OHLoginActivity : DaggerAppCompatActivity() { +class OHLoginActivity : TranslatedDaggerAppCompatActivity() { @Inject internal lateinit var viewModelFactory: ViewModelFactory diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/tidepool/TidepoolFragment.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/tidepool/TidepoolFragment.kt index 827d8c5a2c..2a32b3b643 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/tidepool/TidepoolFragment.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/tidepool/TidepoolFragment.kt @@ -8,6 +8,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.ScrollView +import androidx.core.view.MenuCompat import androidx.core.view.MenuProvider import androidx.lifecycle.Lifecycle import dagger.android.support.DaggerFragment @@ -63,7 +64,7 @@ class TidepoolFragment : DaggerFragment(), MenuProvider { menu.add(Menu.FIRST, ID_MENU_SEND_NOW, 0, rh.gs(R.string.upload_now)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) menu.add(Menu.FIRST, ID_MENU_REMOVE_ALL, 0, rh.gs(R.string.remove_all)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) menu.add(Menu.FIRST, ID_MENU_FULL_SYNC, 0, rh.gs(R.string.full_sync)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) - menu.setGroupDividerEnabled(true) + MenuCompat.setGroupDividerEnabled(menu, true) } override fun onMenuItemSelected(item: MenuItem): Boolean = @@ -120,4 +121,4 @@ class TidepoolFragment : DaggerFragment(), MenuProvider { super.onDestroyView() _binding = null } -} +} \ No newline at end of file diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/tidepool/elements/BloodGlucoseElement.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/tidepool/elements/BloodGlucoseElement.kt index fba3dcb4cc..9bafb71ebd 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/tidepool/elements/BloodGlucoseElement.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/tidepool/elements/BloodGlucoseElement.kt @@ -21,7 +21,7 @@ class BloodGlucoseElement(therapyEvent: TherapyEvent, dateUtil: DateUtil) var value: Int = 0 init { - type = "cbg" + type = "smbg" subType = "manual" // TODO value = if (therapyEvent.glucose != null) Profile.toMgdl(therapyEvent.glucose!!, therapyEvent.glucoseUnit.toMainUnit()).toInt() diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/xdrip/XdripFragment.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/xdrip/XdripFragment.kt index 7e838af329..ca3f64e0ca 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/xdrip/XdripFragment.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/xdrip/XdripFragment.kt @@ -9,6 +9,7 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.core.view.MenuCompat import androidx.core.view.MenuProvider import androidx.lifecycle.Lifecycle import dagger.android.support.DaggerFragment @@ -67,7 +68,7 @@ class XdripFragment : DaggerFragment(), MenuProvider, PluginFragment { override fun onCreateMenu(menu: Menu, inflater: MenuInflater) { menu.add(Menu.FIRST, ID_MENU_CLEAR_LOG, 0, rh.gs(R.string.clear_log)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) menu.add(Menu.FIRST, ID_MENU_FULL_SYNC, 0, rh.gs(R.string.full_sync)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) - menu.setGroupDividerEnabled(true) + MenuCompat.setGroupDividerEnabled(menu, true) } override fun onMenuItemSelected(item: MenuItem): Boolean = diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/xdrip/XdripPlugin.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/xdrip/XdripPlugin.kt index c2a5d9a91f..b683422596 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/xdrip/XdripPlugin.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/xdrip/XdripPlugin.kt @@ -362,10 +362,8 @@ class XdripPlugin @Inject constructor( private fun sendEntries(dataPairs: List, progress: String) { val array = JSONArray() - for (dataPair in dataPairs) { - val data = (dataPair as DataSyncSelector.PairGlucoseValue).value.toXdripJson() - array.put(data) - } + for (dataPair in dataPairs) + (dataPair as DataSyncSelector.PairGlucoseValue?)?.value?.toXdripJson()?.also { gv -> array.put(gv) } rxBus.send(EventXdripNewLog("SENDING", "Sent ${array.length()} BGs ($progress)")) broadcast( Intent(Intents.ACTION_NEW_SGV) diff --git a/plugins/sync/src/main/res/values-bg-rBG/oh_strings.xml b/plugins/sync/src/main/res/values-bg-rBG/oh_strings.xml index 12825caebf..0eefe36b3c 100644 --- a/plugins/sync/src/main/res/values-bg-rBG/oh_strings.xml +++ b/plugins/sync/src/main/res/values-bg-rBG/oh_strings.xml @@ -22,7 +22,7 @@ Условия за ползване Моля прочетете внимателно следващата информация и се съгласете с условията за да продължите. Това е отворен код, който ще копира данните ви в Open Humans. Ние не запазваме никакви права да споделяме вашите данни с трети страни без изричното ви разрешение. Данните, които проектът и приложението получават, се идентифицират чрез случаен идентификатор на потребител и ще бъдат надеждно предадени на Open Humans акаунт с вашето разрешение за този процес. Можете да спрете качването и да изтриете данните за качване по всяко време чрез www.openhumans.org. - Данните не са качени + Данните са качени Стойности на КЗ Болуси Удължен болус @@ -45,7 +45,7 @@ Разбирам и съм съгласен. Качване на данни към Open Humans Последни настройки - На една крачка от прикачването на данните си в Отворено Общество си. Продължаваш ли? + Ти си на една крачка от качването на данните ти в Open Humans. Продължаваш ли? Откажи Продължи Приключва... diff --git a/plugins/sync/src/main/res/values-it-rIT/strings.xml b/plugins/sync/src/main/res/values-it-rIT/strings.xml index 8dd27e5681..3623755394 100644 --- a/plugins/sync/src/main/res/values-it-rIT/strings.xml +++ b/plugins/sync/src/main/res/values-it-rIT/strings.xml @@ -37,6 +37,7 @@ Inserisci l\'API secret di NS (minimo 12 caratteri) Token di accesso NS Token di accesso NS + Token di accesso generato sulla pagina admin di NS (min 17 caratteri) Invia ora Cancella coda Mostra coda @@ -112,6 +113,7 @@ Invia statusline a xDrip+ xDrip+ non installato Calibrazione inviata a xDrip+ + Invia dati glicemia e trattamenti a xDrip+. La sorgente dati \"xDrip+ Sync Follower\" deve essere selezionata e l\'accettazione dei dati deve essere abilitata in: Settings - Inter-app settings - Accept Glucose/Treatments Abilita trasmissioni a xDrip+. diff --git a/plugins/sync/src/test/java/info/nightscout/plugins/sync/nsclient/ReceiverDelegateTest.kt b/plugins/sync/src/test/java/info/nightscout/plugins/sync/nsclient/ReceiverDelegateTest.kt index 6aa5d4f192..73ea5aa992 100644 --- a/plugins/sync/src/test/java/info/nightscout/plugins/sync/nsclient/ReceiverDelegateTest.kt +++ b/plugins/sync/src/test/java/info/nightscout/plugins/sync/nsclient/ReceiverDelegateTest.kt @@ -55,13 +55,13 @@ class ReceiverDelegateTest : TestBase() { Assertions.assertTrue(sut.calculateStatus(EventNetworkChange(ssid = "", mobileConnected = false, wifiConnected = true))) Assertions.assertFalse(sut.calculateStatus(EventNetworkChange())) - `when`(sp.getString(R.string.key_ns_wifi_ssids, "")).thenReturn("test") + `when`(sp.getString(R.string.key_ns_wifi_ssids, "")).thenReturn("test 1") Assertions.assertTrue(sut.calculateStatus(EventNetworkChange(mobileConnected = true, wifiConnected = false, roaming = true))) Assertions.assertTrue(sut.calculateStatus(EventNetworkChange(mobileConnected = true, wifiConnected = false, roaming = false))) Assertions.assertTrue(sut.calculateStatus(EventNetworkChange(ssid = "", mobileConnected = true, wifiConnected = true))) Assertions.assertFalse(sut.calculateStatus(EventNetworkChange(ssid = "", mobileConnected = false, wifiConnected = true))) - Assertions.assertTrue(sut.calculateStatus(EventNetworkChange(ssid = "test", mobileConnected = true, wifiConnected = true))) - Assertions.assertTrue(sut.calculateStatus(EventNetworkChange(ssid = "test", mobileConnected = false, wifiConnected = true))) + Assertions.assertTrue(sut.calculateStatus(EventNetworkChange(ssid = "test 1", mobileConnected = true, wifiConnected = true))) + Assertions.assertTrue(sut.calculateStatus(EventNetworkChange(ssid = "test 1", mobileConnected = false, wifiConnected = true))) Assertions.assertFalse(sut.calculateStatus(EventNetworkChange())) `when`(sp.getBoolean(R.string.key_ns_cellular, true)).thenReturn(false) diff --git a/pump/combo/build.gradle b/pump/combo/build.gradle index b871b5dea7..6705136d6d 100644 --- a/pump/combo/build.gradle +++ b/pump/combo/build.gradle @@ -23,6 +23,6 @@ dependencies { implementation project(':app-wear-shared:shared') // RuffyScripter - api 'com.google.guava:guava:31.1-jre' + api 'com.google.guava:guava:32.0.0-jre' } \ No newline at end of file diff --git a/pump/combov2/build.gradle b/pump/combov2/build.gradle index dce33a98dc..b98303f68b 100644 --- a/pump/combov2/build.gradle +++ b/pump/combov2/build.gradle @@ -32,7 +32,7 @@ dependencies { // // Source: https://github.com/mockk/mockk/issues/685#issuecomment-907076353: // TODO: Revisit this when upgrading kotlinx-datetime - runtimeOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinx_serialization_core_version") + runtimeOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinx_serialization_version") } android { diff --git a/pump/combov2/comboctl/src/androidMain/kotlin/info/nightscout/comboctl/android/AndroidBluetoothInterface.kt b/pump/combov2/comboctl/src/androidMain/kotlin/info/nightscout/comboctl/android/AndroidBluetoothInterface.kt index a0ff751adb..53960d284c 100644 --- a/pump/combov2/comboctl/src/androidMain/kotlin/info/nightscout/comboctl/android/AndroidBluetoothInterface.kt +++ b/pump/combov2/comboctl/src/androidMain/kotlin/info/nightscout/comboctl/android/AndroidBluetoothInterface.kt @@ -7,8 +7,10 @@ import android.content.Intent import android.content.IntentFilter import info.nightscout.comboctl.base.BluetoothAddress import info.nightscout.comboctl.base.BluetoothDevice +import info.nightscout.comboctl.base.BluetoothNotEnabledException import info.nightscout.comboctl.base.BluetoothException import info.nightscout.comboctl.base.BluetoothInterface +import info.nightscout.comboctl.base.BluetoothNotAvailableException import info.nightscout.comboctl.base.LogLevel import info.nightscout.comboctl.base.Logger import info.nightscout.comboctl.base.toBluetoothAddress @@ -35,7 +37,10 @@ private val logger = Logger.get("AndroidBluetoothInterface") * instance is an ideal choice. */ class AndroidBluetoothInterface(private val androidContext: Context) : BluetoothInterface { - private var bluetoothAdapter: SystemBluetoothAdapter? = null + private var _bluetoothAdapter: SystemBluetoothAdapter? = null + private val bluetoothAdapter: SystemBluetoothAdapter + get() = _bluetoothAdapter ?: throw BluetoothNotAvailableException() + private var rfcommServerSocket: SystemBluetoothServerSocket? = null private var discoveryStarted = false private var discoveryBroadcastReceiver: BroadcastReceiver? = null @@ -96,11 +101,16 @@ class AndroidBluetoothInterface(private val androidContext: Context) : Bluetooth else @Suppress("DEPRECATION") getParcelableExtra(name) fun setup() { - val bluetoothManager = androidContext.getSystemService(Context.BLUETOOTH_SERVICE) as SystemBluetoothManager - bluetoothAdapter = bluetoothManager.adapter + val bluetoothManager = androidContext.getSystemService(Context.BLUETOOTH_SERVICE) as? SystemBluetoothManager + _bluetoothAdapter = bluetoothManager?.adapter + + checkIfBluetoothEnabledAndAvailable() val bondedDevices = checkForConnectPermission(androidContext) { - bluetoothAdapter!!.bondedDevices + // The "not enabled" check above is important, because in the disabled + // state, the adapter returns an empty list here. This would mislead + // the logic below into thinking that there are no bonded devices. + bluetoothAdapter.bondedDevices } logger(LogLevel.DEBUG) { "Found ${bondedDevices.size} bonded Bluetooth device(s)" } @@ -180,7 +190,7 @@ class AndroidBluetoothInterface(private val androidContext: Context) : Bluetooth // necessary for correct function, just a detail for sake of completeness.) logger(LogLevel.DEBUG) { "Setting up RFCOMM listener socket" } rfcommServerSocket = checkForConnectPermission(androidContext) { - bluetoothAdapter!!.listenUsingInsecureRfcommWithServiceRecord( + bluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord( sdpServiceName, Constants.sdpSerialPortUUID ) @@ -203,7 +213,7 @@ class AndroidBluetoothInterface(private val androidContext: Context) : Bluetooth logger(LogLevel.DEBUG) { "Closing accepted incoming RFCOMM socket" } try { socket.close() - } catch (e: IOException) { + } catch (_: IOException) { } } } @@ -274,20 +284,24 @@ class AndroidBluetoothInterface(private val androidContext: Context) : Bluetooth stopDiscoveryInternal() } - override fun getDevice(deviceAddress: BluetoothAddress): BluetoothDevice = - AndroidBluetoothDevice(androidContext, bluetoothAdapter!!, deviceAddress) + override fun getDevice(deviceAddress: BluetoothAddress): BluetoothDevice { + checkIfBluetoothEnabledAndAvailable() + return AndroidBluetoothDevice(androidContext, bluetoothAdapter, deviceAddress) + } override fun getAdapterFriendlyName() = - checkForConnectPermission(androidContext) { bluetoothAdapter!!.name } + checkForConnectPermission(androidContext) { bluetoothAdapter.name } ?: throw BluetoothException("Could not get Bluetooth adapter friendly name") - override fun getPairedDeviceAddresses(): Set = - try { + override fun getPairedDeviceAddresses(): Set { + checkIfBluetoothEnabledAndAvailable() + return try { deviceAddressLock.lock() pairedDeviceAddresses.filter { pairedDeviceAddress -> deviceFilterCallback(pairedDeviceAddress) }.toSet() } finally { deviceAddressLock.unlock() } + } private fun stopDiscoveryInternal() { // Close the server socket. This frees RFCOMM resources and ends @@ -320,9 +334,9 @@ class AndroidBluetoothInterface(private val androidContext: Context) : Bluetooth runIfScanPermissionGranted(androidContext) { @SuppressLint("MissingPermission") - if (bluetoothAdapter!!.isDiscovering) { + if (bluetoothAdapter.isDiscovering) { logger(LogLevel.DEBUG) { "Stopping discovery" } - bluetoothAdapter!!.cancelDiscovery() + bluetoothAdapter.cancelDiscovery() } } @@ -332,6 +346,16 @@ class AndroidBluetoothInterface(private val androidContext: Context) : Bluetooth } } + private fun checkIfBluetoothEnabledAndAvailable() { + // Trying to access bluetoothAdapter here if it is currently null will + // automatically cause BluetoothNotAvailableException to be thrown, + // so that case is also covered implicitly by this code. + if (!bluetoothAdapter.isEnabled || (bluetoothAdapter.state != SystemBluetoothAdapter.STATE_ON)) { + logger(LogLevel.ERROR) { "Bluetooth is not enabled" } + throw BluetoothNotEnabledException() + } + } + private fun onAclConnected(intent: Intent, foundNewPairedDevice: (deviceAddress: BluetoothAddress) -> Unit) { // Sanity check in case we get this notification for the // device already and need to avoid duplicate processing. diff --git a/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/base/BluetoothException.kt b/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/base/BluetoothException.kt index f2f73ab9ba..eb7e9026b1 100644 --- a/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/base/BluetoothException.kt +++ b/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/base/BluetoothException.kt @@ -23,3 +23,19 @@ open class BluetoothPermissionException(message: String?, cause: Throwable?) : B constructor(message: String) : this(message, null) constructor(cause: Throwable) : this(null, cause) } + +/** + * Exception thrown when trying to use Bluetooth even though the adapter is not enabled. + * + * Note that unlike [BluetoothNotAvailableException], here, the adapter _does_ exist, + * and is just currently turned off. + */ +open class BluetoothNotEnabledException : BluetoothException("Bluetooth is not enabled") + +/** + * Exception thrown when trying to use Bluetooth even though there no adapter available. + * + * "Not available" typically means that the platform has no Bluetooth hardware, or that + * said hardware is inaccessible. + */ +open class BluetoothNotAvailableException : BluetoothException("Bluetooth is not available - there is no usable adapter") \ No newline at end of file diff --git a/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/base/BluetoothInterface.kt b/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/base/BluetoothInterface.kt index e7eda120fa..cc59e28488 100644 --- a/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/base/BluetoothInterface.kt +++ b/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/base/BluetoothInterface.kt @@ -140,6 +140,10 @@ interface BluetoothInterface { * a Bluetooth subsystem that has been shut down. * @throws BluetoothPermissionException if discovery fails because * scanning and connection permissions are missing. + * @throws BluetoothNotEnabledException if the system's + * Bluetooth adapter is currently not enabled. + * @throws BluetoothNotAvailableException if the system's + * Bluetooth adapter is currently not available. * @throws BluetoothException if discovery fails due to an underlying * Bluetooth issue. */ @@ -172,6 +176,10 @@ interface BluetoothInterface { * * @return BluetoothDevice instance for the device with the * given address + * @throws BluetoothNotEnabledException if the system's + * Bluetooth adapter is currently not enabled. + * @throws BluetoothNotAvailableException if the system's + * Bluetooth adapter is currently not available. * @throws IllegalStateException if the interface is in a state * in which accessing devices is not possible, such as * a Bluetooth subsystem that has been shut down. @@ -183,6 +191,8 @@ interface BluetoothInterface { * * @throws BluetoothPermissionException if getting the adapter name * fails because connection permissions are missing. + * @throws BluetoothNotAvailableException if the system's + * Bluetooth adapter is currently not available. * @throws BluetoothException if getting the adapter name fails * due to an underlying Bluetooth issue. */ @@ -205,6 +215,11 @@ interface BluetoothInterface { * round, it is possible that between the [getPairedDeviceAddresses] * call and the [onDeviceUnpaired] assignment, a device is * unpaired, and thus does not get noticed. + * + * @throws BluetoothNotEnabledException if the system's + * Bluetooth adapter is currently not enabled. + * @throws BluetoothNotAvailableException if the system's + * Bluetooth adapter is currently not available. */ fun getPairedDeviceAddresses(): Set } diff --git a/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/main/Pump.kt b/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/main/Pump.kt index 13e5d845ef..0e76257bde 100644 --- a/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/main/Pump.kt +++ b/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/main/Pump.kt @@ -249,6 +249,7 @@ class Pump( // Used for keeping track of wether an RT alert screen was already dismissed // (necessary since the screen may change its contents but still be the same screen). private var rtScreenAlreadyDismissed = false + private var seenAlertAfterDismissingCounter = 0 // Used in handleAlertScreenContent() to check if the current alert // screen contains the same alert as the previous one. private var lastObservedAlertScreenContent: AlertScreenContent? = null @@ -1876,6 +1877,7 @@ class Pump( suspend fun fetchTDDHistory() = executeCommand>( pumpMode = PumpIO.Mode.REMOTE_TERMINAL, isIdempotent = true, + allowExecutionWhileSuspended = true, description = FetchingTDDHistoryCommandDesc() ) { tddHistoryProgressReporter.reset(Unit) @@ -2401,10 +2403,32 @@ class Pump( // the two button presses, so there is no need to wait // for the second screen - just press twice right away. if (!rtScreenAlreadyDismissed) { - logger(LogLevel.DEBUG) { "Dismissing W$warningCode by short-pressing CHECK twice" } - rtNavigationContext.shortPressButton(RTNavigationButton.CHECK) - rtNavigationContext.shortPressButton(RTNavigationButton.CHECK) + val numRequiredButtonPresses = when (alertScreenContent.state) { + AlertScreenContent.AlertScreenState.TO_SNOOZE -> 2 + AlertScreenContent.AlertScreenState.TO_CONFIRM -> 1 + else -> throw AlertScreenException(alertScreenContent) + } + logger(LogLevel.DEBUG) { "Dismissing W$warningCode by short-pressing CHECK $numRequiredButtonPresses time(s)" } + for (i in 1..numRequiredButtonPresses) + rtNavigationContext.shortPressButton(RTNavigationButton.CHECK) rtScreenAlreadyDismissed = true + } else { + // In rare cases, an alert screen may still show after an alert + // was dismissed. Unfortunately, it is not immediately clear if + // this is the case, because the RT screen updates can come in + // with some temporal jitter. So, to be safe, we only begin to + // again handle alert screens after >10 alert screens were + // observed in sequence. + logger(LogLevel.DEBUG) { "W$warningCode already dismissed" } + seenAlertAfterDismissingCounter++ + if (seenAlertAfterDismissingCounter > 10) { + logger(LogLevel.WARN) { + "Saw an alert screen $seenAlertAfterDismissingCounter time(s) " + + "after having dismissed an alert twice; now again handling alerts" + } + rtScreenAlreadyDismissed = false + seenAlertAfterDismissingCounter = 0 + } } } } diff --git a/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/parser/Parser.kt b/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/parser/Parser.kt index a908ad3e0f..7ad91437a5 100644 --- a/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/parser/Parser.kt +++ b/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/parser/Parser.kt @@ -81,8 +81,17 @@ sealed class MainScreenContent { * Possible contents of alert (= warning/error) screens. */ sealed class AlertScreenContent { - data class Warning(val code: Int) : AlertScreenContent() - data class Error(val code: Int) : AlertScreenContent() + enum class AlertScreenState { + TO_SNOOZE, + TO_CONFIRM, + // Used when the alert is an error. The text in error screens is not + // interpreted, since it is anyway fully up to the user to interpret it. + ERROR_TEXT, + HISTORY_ENTRY + } + + data class Warning(val code: Int, val state: AlertScreenState) : AlertScreenContent() + data class Error(val code: Int, val state: AlertScreenState) : AlertScreenContent() /** * "Content" while the alert symbol & code currently are "blinked out". @@ -1139,8 +1148,9 @@ class AlertScreenParser : Parser() { OptionalParser(SingleGlyphTypeParser(Glyph.LargeSymbol::class)), // warning/error symbol OptionalParser(SingleGlyphTypeParser(Glyph.LargeCharacter::class)), // "W" or "E" OptionalParser(IntegerParser()), // warning/error number - OptionalParser(SingleGlyphTypeParser(Glyph.LargeSymbol::class)), // stop symbol (only with errors) - SingleGlyphParser(Glyph.SmallSymbol(SmallSymbol.CHECK)) + OptionalParser(SingleGlyphTypeParser(Glyph.LargeSymbol::class)), // stop symbol (shown in suspended state) + SingleGlyphParser(Glyph.SmallSymbol(SmallSymbol.CHECK)), + StringParser() // snooze / confirm text ) ).parse(parseContext) @@ -1151,14 +1161,27 @@ class AlertScreenParser : Parser() { return when (parseResult.valueAtOrNull(0)) { Glyph.LargeSymbol(LargeSymbol.WARNING) -> { + val stateString = parseResult.valueAt(4) + val alertState = when (knownScreenTitles[stateString]) { + TitleID.ALERT_TO_SNOOZE -> AlertScreenContent.AlertScreenState.TO_SNOOZE + TitleID.ALERT_TO_CONFIRM -> AlertScreenContent.AlertScreenState.TO_CONFIRM + else -> return ParseResult.Failed + } ParseResult.Value(ParsedScreen.AlertScreen( - AlertScreenContent.Warning(parseResult.valueAt(2)) + AlertScreenContent.Warning(parseResult.valueAt(2), alertState) )) } Glyph.LargeSymbol(LargeSymbol.ERROR) -> { ParseResult.Value(ParsedScreen.AlertScreen( - AlertScreenContent.Error(parseResult.valueAt(2)) + AlertScreenContent.Error( + parseResult.valueAt(2), + // We don't really care about the state string if an error is shown. + // It's not like any logic here will interpret it; that text is + // purely for the user. So, don't bother interpreting it here, and + // just assign a generic ERROR_TEXT state value instead. + AlertScreenContent.AlertScreenState.ERROR_TEXT + ) )) } @@ -1226,6 +1249,7 @@ class TemporaryBasalRatePercentageScreenParser : Parser() { override fun parseImpl(parseContext: ParseContext): ParseResult { val parseResult = SequenceParser( listOf( + OptionalParser(SingleGlyphParser(Glyph.SmallSymbol(SmallSymbol.PERCENT))), SingleGlyphParser(Glyph.LargeSymbol(LargeSymbol.BASAL)), OptionalParser(IntegerParser()), // TBR percentage SingleGlyphParser(Glyph.LargeSymbol(LargeSymbol.PERCENT)), @@ -1654,7 +1678,10 @@ class MyDataErrorDataScreenParser : Parser() { index = index, totalNumEntries = totalNumEntries, timestamp = timestamp, - alert = if (alertType == SmallSymbol.WARNING) AlertScreenContent.Warning(alertNumber) else AlertScreenContent.Error(alertNumber) + alert = if (alertType == SmallSymbol.WARNING) + AlertScreenContent.Warning(alertNumber, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) + else + AlertScreenContent.Error(alertNumber, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ) } diff --git a/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/parser/Pattern.kt b/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/parser/Pattern.kt index dba6ec1f14..c84103c3c9 100644 --- a/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/parser/Pattern.kt +++ b/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/parser/Pattern.kt @@ -1414,6 +1414,15 @@ val glyphPatterns = mapOf( "█ ", "█████" )), + Glyph.SmallCharacter('Ė') to Pattern(arrayOf( + " █ ", + " ", + "█████", + "█ ", + "████ ", + "█ ", + "█████" + )), Glyph.SmallCharacter('ę') to Pattern(arrayOf( "█████", "█ ", diff --git a/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/parser/TitleStrings.kt b/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/parser/TitleStrings.kt index 8b13c08d8e..fcd06ffe94 100644 --- a/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/parser/TitleStrings.kt +++ b/pump/combov2/comboctl/src/commonMain/kotlin/info/nightscout/comboctl/parser/TitleStrings.kt @@ -19,7 +19,9 @@ enum class TitleID { BOLUS_DATA, ERROR_DATA, DAILY_TOTALS, - TBR_DATA + TBR_DATA, + ALERT_TO_SNOOZE, + ALERT_TO_CONFIRM } /** @@ -46,6 +48,8 @@ val knownScreenTitles = mapOf( "ERROR DATA" to TitleID.ERROR_DATA, "DAILY TOTALS" to TitleID.DAILY_TOTALS, "TBR DATA" to TitleID.TBR_DATA, + "TO SNOOZE" to TitleID.ALERT_TO_SNOOZE, + "TO CONFIRM" to TitleID.ALERT_TO_CONFIRM, // Spanish "QUICK INFO" to TitleID.QUICK_INFO, @@ -60,6 +64,8 @@ val knownScreenTitles = mapOf( "DATOS DE ERROR" to TitleID.ERROR_DATA, "TOTALES DIARIOS" to TitleID.DAILY_TOTALS, "DATOS DE DBT" to TitleID.TBR_DATA, + "REPETIR SEÑAL" to TitleID.ALERT_TO_SNOOZE, + "CONFIRMAR" to TitleID.ALERT_TO_CONFIRM, // French "QUICK INFO" to TitleID.QUICK_INFO, @@ -74,6 +80,8 @@ val knownScreenTitles = mapOf( "ERREURS" to TitleID.ERROR_DATA, "QUANTITÉS JOURN." to TitleID.DAILY_TOTALS, "DBT" to TitleID.TBR_DATA, + "RAPPEL TARD" to TitleID.ALERT_TO_SNOOZE, // actually, the text is "RAPPEL + TARD", but the + symbol is ignored to simplify parsing + "POUR CONFIRMER" to TitleID.ALERT_TO_CONFIRM, // Italian "QUICK INFO" to TitleID.QUICK_INFO, @@ -88,6 +96,8 @@ val knownScreenTitles = mapOf( "MEMORIA ALLARMI" to TitleID.ERROR_DATA, "TOTALI GIORNATA" to TitleID.DAILY_TOTALS, "MEMORIA PBT" to TitleID.TBR_DATA, + "RIPETI ALLARME" to TitleID.ALERT_TO_SNOOZE, + "PER CONFERMARE" to TitleID.ALERT_TO_CONFIRM, // Russian "QUICK INFO" to TitleID.QUICK_INFO, @@ -102,6 +112,8 @@ val knownScreenTitles = mapOf( "ДАHHЫE OБ O ИБ." to TitleID.ERROR_DATA, "CУTOЧHЫE ДOЗЫ" to TitleID.DAILY_TOTALS, "ДАHHЫE O BБC" to TitleID.TBR_DATA, + "BЫKЛ. ЗBУK" to TitleID.ALERT_TO_SNOOZE, + "ПOДTBEPДИTЬ" to TitleID.ALERT_TO_CONFIRM, // Turkish "QUICK INFO" to TitleID.QUICK_INFO, @@ -116,6 +128,8 @@ val knownScreenTitles = mapOf( "HATA VERİLERİ" to TitleID.ERROR_DATA, "GÜNLÜK TOPLAM" to TitleID.DAILY_TOTALS, "GBH VERİLERİ" to TitleID.TBR_DATA, + "ERTELE" to TitleID.ALERT_TO_SNOOZE, + "ONAYLA" to TitleID.ALERT_TO_CONFIRM, // Polish "QUICK INFO" to TitleID.QUICK_INFO, @@ -130,6 +144,8 @@ val knownScreenTitles = mapOf( "DANE BŁĘDU" to TitleID.ERROR_DATA, "DZIEN. D. CAŁK." to TitleID.DAILY_TOTALS, "DANE TDP" to TitleID.TBR_DATA, + "ABY WYCISZYĆ" to TitleID.ALERT_TO_SNOOZE, + "ABY POTWIERDZ." to TitleID.ALERT_TO_CONFIRM, // Czech "QUICK INFO" to TitleID.QUICK_INFO, @@ -144,6 +160,8 @@ val knownScreenTitles = mapOf( "ÚDAJE CHYB" to TitleID.ERROR_DATA, "CELK. DEN. DÁVKY" to TitleID.DAILY_TOTALS, "ÚDAJE DBD" to TitleID.TBR_DATA, + "ODLOŽIT" to TitleID.ALERT_TO_SNOOZE, + "POTVRDIT" to TitleID.ALERT_TO_CONFIRM, // Hungarian "QUICK INFO" to TitleID.QUICK_INFO, @@ -158,6 +176,8 @@ val knownScreenTitles = mapOf( "HIBAADATOK" to TitleID.ERROR_DATA, "NAPI TELJES" to TitleID.DAILY_TOTALS, "TBR-ADATOK" to TitleID.TBR_DATA, + "NÉMÍTÁS" to TitleID.ALERT_TO_SNOOZE, + "JÓVÁHAGYÁS" to TitleID.ALERT_TO_CONFIRM, // Slovak "QUICK INFO" to TitleID.QUICK_INFO, @@ -172,6 +192,8 @@ val knownScreenTitles = mapOf( "DÁTA O CHYBÁCH" to TitleID.ERROR_DATA, "SÚČTY DŇA" to TitleID.DAILY_TOTALS, "DBD DÁTA" to TitleID.TBR_DATA, + "STLMI" to TitleID.ALERT_TO_SNOOZE, + "POTVRDI" to TitleID.ALERT_TO_CONFIRM, // Romanian "QUICK INFO" to TitleID.QUICK_INFO, @@ -186,6 +208,8 @@ val knownScreenTitles = mapOf( "DATE EROARE" to TitleID.ERROR_DATA, "TOTALURI ZILNICE" to TitleID.DAILY_TOTALS, "DATE RBT" to TitleID.TBR_DATA, + "OPRIRE SONERIE" to TitleID.ALERT_TO_SNOOZE, + "CONFIRMARE" to TitleID.ALERT_TO_CONFIRM, // Croatian "QUICK INFO" to TitleID.QUICK_INFO, @@ -200,6 +224,8 @@ val knownScreenTitles = mapOf( "PODACI O GREŠK." to TitleID.ERROR_DATA, "UKUPNE DNEV.DOZE" to TitleID.DAILY_TOTALS, "PODACI O PBD-U" to TitleID.TBR_DATA, + "ZA ODGODU" to TitleID.ALERT_TO_SNOOZE, + "ZA POTVRDU" to TitleID.ALERT_TO_CONFIRM, // Dutch "QUICK INFO" to TitleID.QUICK_INFO, @@ -214,6 +240,8 @@ val knownScreenTitles = mapOf( "FOUTENGEGEVENS" to TitleID.ERROR_DATA, "DAGTOTALEN" to TitleID.DAILY_TOTALS, "TBD-GEGEVENS" to TitleID.TBR_DATA, + "UITSTELLEN" to TitleID.ALERT_TO_SNOOZE, + "BEVESTIGEN" to TitleID.ALERT_TO_CONFIRM, // Greek "QUICK INFO" to TitleID.QUICK_INFO, @@ -228,6 +256,8 @@ val knownScreenTitles = mapOf( "ΔEΔOМ. ΣΦАΛМАTΩN" to TitleID.ERROR_DATA, "HМEPHΣIO ΣΥNOΛO" to TitleID.DAILY_TOTALS, "ΔEΔOМENА П.B.P." to TitleID.TBR_DATA, + "ANAΣTOΛH" to TitleID.ALERT_TO_SNOOZE, + "EПIBEBАIΩΣH" to TitleID.ALERT_TO_CONFIRM, // Finnish "QUICK INFO" to TitleID.QUICK_INFO, @@ -242,6 +272,8 @@ val knownScreenTitles = mapOf( "HÄLYTYSTIEDOT" to TitleID.ERROR_DATA, "PÄIV. KOK.ANNOS" to TitleID.DAILY_TOTALS, "TBA - TIEDOT" to TitleID.TBR_DATA, + "ILMOITA MYÖH." to TitleID.ALERT_TO_SNOOZE, + "VAHVISTA" to TitleID.ALERT_TO_CONFIRM, // Norwegian "QUICK INFO" to TitleID.QUICK_INFO, @@ -256,6 +288,8 @@ val knownScreenTitles = mapOf( "FEILDATA" to TitleID.ERROR_DATA, "DØGNMENGDE" to TitleID.DAILY_TOTALS, "MBD-DATA" to TitleID.TBR_DATA, + "FOR Å SLUMRE" to TitleID.ALERT_TO_SNOOZE, + "FOR Å BEKREFTE" to TitleID.ALERT_TO_CONFIRM, // Portuguese "QUICK INFO" to TitleID.QUICK_INFO, @@ -271,6 +305,8 @@ val knownScreenTitles = mapOf( "DADOS DE ERROS" to TitleID.ERROR_DATA, "DADOS DE ALARMES" to TitleID.ERROR_DATA, "TOTAIS DIÁRIOS" to TitleID.DAILY_TOTALS, "DADOS DBT" to TitleID.TBR_DATA, + "PARA SILENCIAR" to TitleID.ALERT_TO_SNOOZE, + "PARA CONFIRMAR" to TitleID.ALERT_TO_CONFIRM, // Swedish "QUICK INFO" to TitleID.QUICK_INFO, @@ -285,6 +321,8 @@ val knownScreenTitles = mapOf( "FELDATA" to TitleID.ERROR_DATA, "DYGNSHISTORIK" to TitleID.DAILY_TOTALS, "TBD DATA" to TitleID.TBR_DATA, + "SNOOZE" to TitleID.ALERT_TO_SNOOZE, + "BEKRÄFTA" to TitleID.ALERT_TO_CONFIRM, // Danish "QUICK INFO" to TitleID.QUICK_INFO, @@ -299,6 +337,8 @@ val knownScreenTitles = mapOf( "FEJLDATA" to TitleID.ERROR_DATA, "DAGLIG TOTAL" to TitleID.DAILY_TOTALS, "MBR-DATA" to TitleID.TBR_DATA, + "FOR AT UDSÆTTE" to TitleID.ALERT_TO_SNOOZE, + "FOR GODKEND" to TitleID.ALERT_TO_CONFIRM, // German "QUICK INFO" to TitleID.QUICK_INFO, @@ -313,6 +353,40 @@ val knownScreenTitles = mapOf( "FEHLERMELDUNGEN" to TitleID.ERROR_DATA, "TAGESGESAMTMENGE" to TitleID.DAILY_TOTALS, "TBR-INFORMATION" to TitleID.TBR_DATA, + "NEU ERINNERN" to TitleID.ALERT_TO_SNOOZE, + "BESTÄTIGEN" to TitleID.ALERT_TO_CONFIRM, + + // Slovenian + "QUICK INFO" to TitleID.QUICK_INFO, + "ODSTOTEK ZBO" to TitleID.TBR_PERCENTAGE, + "TRAJANJE ZBO" to TitleID.TBR_DURATION, + "URA" to TitleID.HOUR, + "MINUTE" to TitleID.MINUTE, + "LETO" to TitleID.YEAR, + "MESEC" to TitleID.MONTH, + "DAN" to TitleID.DAY, + "PODATKI O BOLUSU" to TitleID.BOLUS_DATA, + "PODATKI O NAPAKI" to TitleID.ERROR_DATA, + "DNEVNA PORABA" to TitleID.DAILY_TOTALS, + "PODATKI O ZBO" to TitleID.TBR_DATA, + "UTIŠANJE" to TitleID.ALERT_TO_SNOOZE, + "POTRDITEV" to TitleID.ALERT_TO_CONFIRM, + + // Lithuanian + "QUICK INFO" to TitleID.QUICK_INFO, + "TBR REIKŠMĖS" to TitleID.TBR_PERCENTAGE, + "TBR TRUKMĖ" to TitleID.TBR_DURATION, + "VALANDA" to TitleID.HOUR, + "MINUTĖ" to TitleID.MINUTE, + "METAI" to TitleID.YEAR, + "MĖNUO" to TitleID.MONTH, + "DIENA" to TitleID.DAY, + "BOLIUSO DUOMENYS" to TitleID.BOLUS_DATA, + "KLAIDOS DUOMENYS" to TitleID.ERROR_DATA, + "BENDR. DIENOS K." to TitleID.DAILY_TOTALS, + "TBR DUOMENYS" to TitleID.TBR_DATA, + "NUTILDYTI" to TitleID.ALERT_TO_SNOOZE, + "PATVIRTINTI" to TitleID.ALERT_TO_CONFIRM, // Some pumps came preconfigured with a different quick info name "ACCU CHECK SPIRIT" to TitleID.QUICK_INFO diff --git a/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/main/ParsedDisplayFrameStreamTest.kt b/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/main/ParsedDisplayFrameStreamTest.kt index 0afd398ceb..deeab186a4 100644 --- a/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/main/ParsedDisplayFrameStreamTest.kt +++ b/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/main/ParsedDisplayFrameStreamTest.kt @@ -13,7 +13,7 @@ import info.nightscout.comboctl.parser.ParsedScreen import info.nightscout.comboctl.parser.testFrameMainScreenWithTimeSeparator import info.nightscout.comboctl.parser.testFrameMainScreenWithoutTimeSeparator import info.nightscout.comboctl.parser.testFrameStandardBolusMenuScreen -import info.nightscout.comboctl.parser.testFrameTbrDurationEnglishScreen +import info.nightscout.comboctl.parser.TbrPercentageAndDurationScreens import info.nightscout.comboctl.parser.testFrameTemporaryBasalRateNoPercentageScreen import info.nightscout.comboctl.parser.testFrameTemporaryBasalRatePercentage110Screen import info.nightscout.comboctl.parser.testFrameW6CancelTbrWarningScreen @@ -300,7 +300,7 @@ class ParsedDisplayFrameStreamTest { // We expect normal parsing behavior. stream.feedDisplayFrame(testFrameW6CancelTbrWarningScreen) val parsedWarningFrame = stream.getParsedDisplayFrame(processAlertScreens = false) - assertEquals(ParsedScreen.AlertScreen(AlertScreenContent.Warning(6)), parsedWarningFrame!!.parsedScreen) + assertEquals(ParsedScreen.AlertScreen(AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)), parsedWarningFrame!!.parsedScreen) // Feed a W6 screen, but with alert screen detection enabled. // We expect the alert screen to be detected and an exception @@ -328,7 +328,7 @@ class ParsedDisplayFrameStreamTest { val displayFrameList = listOf( testFrameTemporaryBasalRatePercentage110Screen, testFrameTemporaryBasalRateNoPercentageScreen, - testFrameTbrDurationEnglishScreen + TbrPercentageAndDurationScreens.testFrameTbrDurationEnglishScreen ) val parsedFrameList = mutableListOf() diff --git a/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/main/RTNavigationTest.kt b/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/main/RTNavigationTest.kt index 387fd7cd8b..7999bb184e 100644 --- a/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/main/RTNavigationTest.kt +++ b/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/main/RTNavigationTest.kt @@ -723,7 +723,7 @@ class RTNavigationTest { )), ParsedScreen.BasalRate1ProgrammingMenuScreen, ParsedScreen.BasalRate2ProgrammingMenuScreen, - ParsedScreen.AlertScreen(AlertScreenContent.Warning(code = 6)), + ParsedScreen.AlertScreen(AlertScreenContent.Warning(code = 6, AlertScreenContent.AlertScreenState.TO_SNOOZE)), ParsedScreen.BasalRate3ProgrammingMenuScreen, ParsedScreen.BasalRate4ProgrammingMenuScreen, ParsedScreen.BasalRate5ProgrammingMenuScreen @@ -864,7 +864,7 @@ class RTNavigationTest { ParsedScreen.TemporaryBasalRatePercentageScreen(170, remainingDurationInMinutes = 30), ParsedScreen.TemporaryBasalRatePercentageScreen(170, remainingDurationInMinutes = 30), ParsedScreen.TemporaryBasalRatePercentageScreen(170, remainingDurationInMinutes = 30), - ParsedScreen.AlertScreen(AlertScreenContent.Warning(code = 6)), + ParsedScreen.AlertScreen(AlertScreenContent.Warning(code = 6, AlertScreenContent.AlertScreenState.TO_SNOOZE)), ParsedScreen.TemporaryBasalRatePercentageScreen(160, remainingDurationInMinutes = 30), ParsedScreen.TemporaryBasalRatePercentageScreen(160, remainingDurationInMinutes = 30) )) diff --git a/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/parser/ParserTest.kt b/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/parser/ParserTest.kt index 3d342a7599..c53cfed2d1 100644 --- a/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/parser/ParserTest.kt +++ b/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/parser/ParserTest.kt @@ -679,16 +679,16 @@ class ParserTest { assertEquals(ParseResult.Value::class, result::class) val alertScreen = (result as ParseResult.Value<*>).value as ParsedScreen.AlertScreen - assertEquals(AlertScreenContent.Warning(6), alertScreen.content) + assertEquals(AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE), alertScreen.content) } @Test fun checkW8CancelBolusWarningScreenParsing() { val testScreens = listOf( Pair(testFrameW8CancelBolusWarningScreen0, AlertScreenContent.None), - Pair(testFrameW8CancelBolusWarningScreen1, AlertScreenContent.Warning(8)), + Pair(testFrameW8CancelBolusWarningScreen1, AlertScreenContent.Warning(8, AlertScreenContent.AlertScreenState.TO_SNOOZE)), Pair(testFrameW8CancelBolusWarningScreen2, AlertScreenContent.None), - Pair(testFrameW8CancelBolusWarningScreen3, AlertScreenContent.Warning(8)) + Pair(testFrameW8CancelBolusWarningScreen3, AlertScreenContent.Warning(8, AlertScreenContent.AlertScreenState.TO_CONFIRM)) ) for (testScreen in testScreens) { @@ -704,7 +704,25 @@ class ParserTest { fun checkE2BatteryEmptyErrorScreenParsing() { val testScreens = listOf( Pair(testFrameE2BatteryEmptyErrorScreen0, AlertScreenContent.None), - Pair(testFrameE2BatteryEmptyErrorScreen1, AlertScreenContent.Error(2)) + Pair(testFrameE2BatteryEmptyErrorScreen1, AlertScreenContent.Error(2, AlertScreenContent.AlertScreenState.ERROR_TEXT)) + ) + + for (testScreen in testScreens) { + val testContext = TestContext(testScreen.first, 0, skipTitleString = true) + val result = AlertScreenParser().parse(testContext.parseContext) + assertEquals(ParseResult.Value::class, result::class) + val screen = (result as ParseResult.Value<*>).value as ParsedScreen.AlertScreen + assertEquals(testScreen.second, screen.content) + } + } + + @Test + fun checkE4OcclusionErrorScreenParsing() { + val testScreens = listOf( + Pair(testFrameE4OcclusionErrorScreen0, AlertScreenContent.Error(4, AlertScreenContent.AlertScreenState.ERROR_TEXT)), + Pair(testFrameE4OcclusionErrorScreen1, AlertScreenContent.None), + Pair(testFrameE4OcclusionErrorScreen2, AlertScreenContent.Error(4, AlertScreenContent.AlertScreenState.ERROR_TEXT)), + Pair(testFrameE4OcclusionErrorScreen3, AlertScreenContent.None), ) for (testScreen in testScreens) { @@ -722,26 +740,28 @@ class ParserTest { Pair(testFrameTemporaryBasalRatePercentage100Screen, 100), Pair(testFrameTemporaryBasalRatePercentage110Screen, 110), Pair(testFrameTemporaryBasalRateNoPercentageScreen, null), - Pair(testFrameTbrPercentageEnglishScreen, 110), - Pair(testFrameTbrPercentageSpanishScreen, 110), - Pair(testFrameTbrPercentageFrenchScreen, 110), - Pair(testFrameTbrPercentageItalianScreen, 110), - Pair(testFrameTbrPercentageRussianScreen, 110), - Pair(testFrameTbrPercentageTurkishScreen, 110), - Pair(testFrameTbrPercentagePolishScreen, 100), - Pair(testFrameTbrPercentageCzechScreen, 110), - Pair(testFrameTbrPercentageHungarianScreen, 110), - Pair(testFrameTbrPercentageSlovakScreen, 110), - Pair(testFrameTbrPercentageRomanianScreen, 110), - Pair(testFrameTbrPercentageCroatianScreen, 110), - Pair(testFrameTbrPercentageDutchScreen, 110), - Pair(testFrameTbrPercentageGreekScreen, 110), - Pair(testFrameTbrPercentageFinnishScreen, 110), - Pair(testFrameTbrPercentageNorwegianScreen, 110), - Pair(testFrameTbrPercentagePortugueseScreen, 110), - Pair(testFrameTbrPercentageSwedishScreen, 110), - Pair(testFrameTbrPercentageDanishScreen, 110), - Pair(testFrameTbrPercentageGermanScreen, 110) + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageEnglishScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageSpanishScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageFrenchScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageItalianScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageRussianScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageTurkishScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentagePolishScreen, 100), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageCzechScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageHungarianScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageSlovakScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageRomanianScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageCroatianScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageDutchScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageGreekScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageFinnishScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageNorwegianScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentagePortugueseScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageSwedishScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageDanishScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageGermanScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageSlovenianScreen, 110), + Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageLithuanianScreen, 110), ) for (testScreen in testScreens) { @@ -769,26 +789,28 @@ class ParserTest { fun checkTemporaryBasalRateDurationScreenParsing() { val testScreens = listOf( Pair(testFrameTbrDurationNoDurationScreen, null), - Pair(testFrameTbrDurationEnglishScreen, 30), - Pair(testFrameTbrDurationSpanishScreen, 30), - Pair(testFrameTbrDurationFrenchScreen, 30), - Pair(testFrameTbrDurationItalianScreen, 30), - Pair(testFrameTbrDurationRussianScreen, 30), - Pair(testFrameTbrDurationTurkishScreen, 30), - Pair(testFrameTbrDurationPolishScreen, 30), - Pair(testFrameTbrDurationCzechScreen, 30), - Pair(testFrameTbrDurationHungarianScreen, 30), - Pair(testFrameTbrDurationSlovakScreen, 30), - Pair(testFrameTbrDurationRomanianScreen, 30), - Pair(testFrameTbrDurationCroatianScreen, 30), - Pair(testFrameTbrDurationDutchScreen, 30), - Pair(testFrameTbrDurationGreekScreen, 30), - Pair(testFrameTbrDurationFinnishScreen, 30), - Pair(testFrameTbrDurationNorwegianScreen, 30), - Pair(testFrameTbrDurationPortugueseScreen, 30), - Pair(testFrameTbrDurationSwedishScreen, 30), - Pair(testFrameTbrDurationDanishScreen, 30), - Pair(testFrameTbrDurationGermanScreen, 30) + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationEnglishScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationSpanishScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationFrenchScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationItalianScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationRussianScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationTurkishScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationPolishScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationCzechScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationHungarianScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationSlovakScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationRomanianScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationCroatianScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationDutchScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationGreekScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationFinnishScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationNorwegianScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationPortugueseScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationSwedishScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationDanishScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationGermanScreen, 30), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationSlovenianScreen, 15), + Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationLithuanianScreen, 15), ) for (testScreen in testScreens) { @@ -927,6 +949,18 @@ class ParserTest { Pair(testTimeAndDateSettingsMonthGermanScreen, ParsedScreen.TimeAndDateSettingsMonthScreen(4)), Pair(testTimeAndDateSettingsDayGermanScreen, ParsedScreen.TimeAndDateSettingsDayScreen(21)), + Pair(testTimeAndDateSettingsHourSlovenianScreen, ParsedScreen.TimeAndDateSettingsHourScreen(19)), + Pair(testTimeAndDateSettingsMinuteSlovenianScreen, ParsedScreen.TimeAndDateSettingsMinuteScreen(50)), + Pair(testTimeAndDateSettingsYearSlovenianScreen, ParsedScreen.TimeAndDateSettingsYearScreen(2023)), + Pair(testTimeAndDateSettingsMonthSlovenianScreen, ParsedScreen.TimeAndDateSettingsMonthScreen(3)), + Pair(testTimeAndDateSettingsDaySlovenianScreen, ParsedScreen.TimeAndDateSettingsDayScreen(8)), + + Pair(testTimeAndDateSettingsHourLithuanianScreen, ParsedScreen.TimeAndDateSettingsHourScreen(20)), + Pair(testTimeAndDateSettingsMinuteLithuanianScreen, ParsedScreen.TimeAndDateSettingsMinuteScreen(16)), + Pair(testTimeAndDateSettingsYearLithuanianScreen, ParsedScreen.TimeAndDateSettingsYearScreen(2023)), + Pair(testTimeAndDateSettingsMonthLithuanianScreen, ParsedScreen.TimeAndDateSettingsMonthScreen(3)), + Pair(testTimeAndDateSettingsDayLithuanianScreen, ParsedScreen.TimeAndDateSettingsDayScreen(8)), + // Extra test to verify that a *minute* 24 is not incorrectly interpreted // as an *hour* 24 and thus translated to 0 (this is done because the Combo // may show midnight as both hour 0 and hour 24). @@ -963,7 +997,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 1, dayOfMonth = 28, hour = 11, minute = 0, second = 0), - alert = AlertScreenContent.Warning(6) + alert = AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -994,7 +1028,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 1, dayOfMonth = 28, hour = 11, minute = 0, second = 0), - alert = AlertScreenContent.Warning(6) + alert = AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1025,7 +1059,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0), - alert = AlertScreenContent.Warning(7) + alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1056,7 +1090,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0), - alert = AlertScreenContent.Warning(7) + alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1087,7 +1121,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0), - alert = AlertScreenContent.Warning(7) + alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1118,7 +1152,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0), - alert = AlertScreenContent.Warning(7) + alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1149,7 +1183,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0), - alert = AlertScreenContent.Warning(7) + alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1180,7 +1214,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0), - alert = AlertScreenContent.Warning(7) + alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1211,7 +1245,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0), - alert = AlertScreenContent.Warning(7) + alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1242,7 +1276,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0), - alert = AlertScreenContent.Warning(7) + alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1273,7 +1307,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0), - alert = AlertScreenContent.Warning(7) + alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1304,7 +1338,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0), - alert = AlertScreenContent.Warning(1) + alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1335,7 +1369,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0), - alert = AlertScreenContent.Warning(1) + alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1366,7 +1400,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0), - alert = AlertScreenContent.Warning(1) + alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1397,7 +1431,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0), - alert = AlertScreenContent.Warning(1) + alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1428,7 +1462,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0), - alert = AlertScreenContent.Warning(1) + alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1459,7 +1493,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0), - alert = AlertScreenContent.Warning(1) + alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1490,7 +1524,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0), - alert = AlertScreenContent.Warning(1) + alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1521,7 +1555,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0), - alert = AlertScreenContent.Warning(1) + alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1552,7 +1586,7 @@ class ParserTest { ParsedScreen.MyDataErrorDataScreen( index = 1, totalNumEntries = 30, timestamp = LocalDateTime(year = 0, monthNumber = 1, dayOfMonth = 28, hour = 11, minute = 0, second = 0), - alert = AlertScreenContent.Warning(6) + alert = AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) ) ), Pair( @@ -1569,7 +1603,69 @@ class ParserTest { timestamp = LocalDateTime(year = 0, monthNumber = 1, dayOfMonth = 28, hour = 11, minute = 0, second = 0), percentage = 110, durationInMinutes = 0 ) - ) + ), + Pair( + testMyDataBolusDataSlovenianScreen, + ParsedScreen.MyDataBolusDataScreen( + index = 1, totalNumEntries = 30, + timestamp = LocalDateTime(year = 0, monthNumber = 1, dayOfMonth = 1, hour = 23, minute = 21, second = 0), + bolusAmount = 3200, bolusType = MyDataBolusType.MULTI_WAVE, durationInMinutes = 43 + ) + ), + Pair( + testMyDataErrorDataSlovenianScreen, + ParsedScreen.MyDataErrorDataScreen( + index = 1, totalNumEntries = 30, + timestamp = LocalDateTime(year = 0, monthNumber = 3, dayOfMonth = 8, hour = 17, minute = 31, second = 0), + alert = AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) + ) + ), + Pair( + testMyDataDailyTotalsSlovenianScreen, + ParsedScreen.MyDataDailyTotalsScreen( + index = 1, totalNumEntries = 30, date = LocalDate(year = 0, dayOfMonth = 8, monthNumber = 3), + totalDailyAmount = 32100 + ) + ), + Pair( + testMyDataTbrDataSlovenianScreen, + ParsedScreen.MyDataTbrDataScreen( + index = 1, totalNumEntries = 30, + timestamp = LocalDateTime(year = 0, monthNumber = 3, dayOfMonth = 8, hour = 17, minute = 31, second = 0), + percentage = 110, durationInMinutes = 1 + ) + ), + Pair( + testMyDataBolusDataLithuanianScreen, + ParsedScreen.MyDataBolusDataScreen( + index = 1, totalNumEntries = 30, + timestamp = LocalDateTime(year = 0, monthNumber = 1, dayOfMonth = 1, hour = 23, minute = 21, second = 0), + bolusAmount = 3200, bolusType = MyDataBolusType.MULTI_WAVE, durationInMinutes = 43 + ) + ), + Pair( + testMyDataErrorDataLithuanianScreen, + ParsedScreen.MyDataErrorDataScreen( + index = 1, totalNumEntries = 30, + timestamp = LocalDateTime(year = 0, monthNumber = 3, dayOfMonth = 8, hour = 20, minute = 6, second = 0), + alert = AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.HISTORY_ENTRY) + ) + ), + Pair( + testMyDataDailyTotalsLithuanianScreen, + ParsedScreen.MyDataDailyTotalsScreen( + index = 1, totalNumEntries = 30, date = LocalDate(year = 0, dayOfMonth = 8, monthNumber = 3), + totalDailyAmount = 33600 + ) + ), + Pair( + testMyDataTbrDataLithuanianScreen, + ParsedScreen.MyDataTbrDataScreen( + index = 1, totalNumEntries = 30, + timestamp = LocalDateTime(year = 0, monthNumber = 3, dayOfMonth = 8, hour = 20, minute = 6, second = 0), + percentage = 110, durationInMinutes = 1 + ) + ), ) for (testScreen in testScreens) { @@ -1595,6 +1691,196 @@ class ParserTest { } } + @Test + fun checkAlertScreenTextParsing() { + val testScreens = listOf( + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextEnglishScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextEnglishScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextSpanishScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextSpanishScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextFrenchScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextFrenchScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextItalianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextItalianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextRussianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextRussianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextTurkishScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextTurkishScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextPolishScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextPolishScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextCzechScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextCzechScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextHungarianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextHungarianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextSlovakScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextSlovakScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextRomanianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextRomanianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextCroatianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextCroatianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextDutchScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextDutchScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextGreekScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextGreekScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextFinnishScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextFinnishScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextNorwegianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextNorwegianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextPortugueseScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextPortugueseScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextSwedishScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextSwedishScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextDanishScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextDanishScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextGermanScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextGermanScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextSlovenianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextSlovenianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextLithuanianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE) + ), + Pair( + AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextLithuanianScreen, + AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM) + ), + ) + + for (testScreen in testScreens) { + val testContext = TestContext(testScreen.first, 0, skipTitleString = true) + val result = AlertScreenParser().parse(testContext.parseContext) + + assertEquals(ParseResult.Value::class, result::class) + val alertScreen = (result as ParseResult.Value<*>).value as ParsedScreen.AlertScreen + assertEquals(testScreen.second, alertScreen.content) + } + } @Test fun checkToplevelScreenParsing() { val testScreens = listOf( diff --git a/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/parser/TestDisplayFrames.kt b/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/parser/TestDisplayFrames.kt index 73eabc7362..9ed1dae19a 100644 --- a/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/parser/TestDisplayFrames.kt +++ b/pump/combov2/comboctl/src/jvmTest/kotlin/info/nightscout/comboctl/parser/TestDisplayFrames.kt @@ -1700,6 +1700,146 @@ val testFrameE2BatteryEmptyErrorScreen1 = makeDisplayFrame(arrayOf( " ████ █████ ████ █ █ █ █ ███ ████ █████ █ █ " )) +val testFrameE4OcclusionErrorScreen0 = makeDisplayFrame(arrayOf( + "█ █ █████ ████ ████ █████ ███ ████ █████ █ █ █ █ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + "█ █ ████ ████ ███ █ █ █ ████ ████ █ █ █ █ █ █ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █████ █ █ ████ █ ███ █ █ ███ █ █ ████ ", + " ", + " █████ ████████ ██ ████████ ", + " █████████ ██ ███ ██████████ ", + " ███████████ ██ ███ ████████████ ", + " ███ █████ ███ ██ ████ ██████████████ ", + " ██ ███ ██ ██ █ ██ ████████████████ ", + " ████ █ ████ ██ ██ ██ █ █ █ █ ██ ", + " █████ █████ ██ █ ██ █ ███ ██ █ █ █ █ ", + " ██████ ██████ ███████ ██ ██ █ ██ ██ █ █ ██ ", + " █████ █████ ██ ██ ██ ██ ██ ██ █ █ ███ ", + " ████ █ ████ ██ ████████ █ ██ ██ █ ███ ", + " ██ ███ ██ ██ ██ ████████████████ ", + " ███ █████ ███ ██ ██ ██████████████ ", + " ███████████ ██ ██ ████████████ ", + " █████████ ██ ██ ██████████ ", + " █████ ████████ ██ ████████ ", + " ", + " ", + " █ ████ █████ █ █ █ █ █ █ ████ ███ █ █ █ █ █████ █████ █ █ ", + " ██ █ █ █ █ ██ ██ ██ ██ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", + "███ ███ █ █ █ █ █ █ █ █ █ ███ █ █████ █████ █ █ ████ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ████ █ ███ █ █ █ █ ████ ███ █ █ █ █ █████ █ █████ █ █ " +)) + +val testFrameE4OcclusionErrorScreen1 = makeDisplayFrame(arrayOf( + "█ █ █████ ████ ████ █████ ███ ████ █████ █ █ █ █ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + "█ █ ████ ████ ███ █ █ █ ████ ████ █ █ █ █ █ █ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █████ █ █ ████ █ ███ █ █ ███ █ █ ████ ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " █ ████ █████ █ █ █ █ █ █ ████ ███ █ █ █ █ █████ █████ █ █ ", + " ██ █ █ █ █ ██ ██ ██ ██ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", + "███ ███ █ █ █ █ █ █ █ █ █ ███ █ █████ █████ █ █ ████ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ████ █ ███ █ █ █ █ ████ ███ █ █ █ █ █████ █ █████ █ █ " +)) + +val testFrameE4OcclusionErrorScreen2 = makeDisplayFrame(arrayOf( + "█ █ █████ ████ ████ █████ ███ ████ █████ █ █ █ █ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + "█ █ ████ ████ ███ █ █ █ ████ ████ █ █ █ █ █ █ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █████ █ █ ████ █ ███ █ █ ███ █ █ ████ ", + " ", + " █████ ████████ ██ ████████ ", + " █████████ ██ ███ ██████████ ", + " ███████████ ██ ███ ████████████ ", + " ███ █████ ███ ██ ████ ██████████████ ", + " ██ ███ ██ ██ █ ██ ████████████████ ", + " ████ █ ████ ██ ██ ██ █ █ █ █ ██ ", + " █████ █████ ██ █ ██ █ ███ ██ █ █ █ █ ", + " ██████ ██████ ███████ ██ ██ █ ██ ██ █ █ ██ ", + " █████ █████ ██ ██ ██ ██ ██ ██ █ █ ███ ", + " ████ █ ████ ██ ████████ █ ██ ██ █ ███ ", + " ██ ███ ██ ██ ██ ████████████████ ", + " ███ █████ ███ ██ ██ ██████████████ ", + " ███████████ ██ ██ ████████████ ", + " █████████ ██ ██ ██████████ ", + " █████ ████████ ██ ████████ ", + " ", + " ", + " █ ████ █████ ████ █████ █ █ █████ ███ ███ █████ █ █ ", + " ██ █ █ █ █ █ ███ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", + "███ ████ ████ ███ █ █ █ █ █ █ ███ ████ █ █ █ ", + " █ █ █ █ █ █ █████ █ █ █ █ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ████ █████ ████ █ █ █ █ ███ ████ █████ █ █ " +)) + +val testFrameE4OcclusionErrorScreen3 = makeDisplayFrame(arrayOf( + "█ █ █████ ████ ████ █████ ███ ████ █████ █ █ █ █ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + "█ █ ████ ████ ███ █ █ █ ████ ████ █ █ █ █ █ █ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █████ █ █ ████ █ ███ █ █ ███ █ █ ████ ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " █ ████ █████ ████ █████ █ █ █████ ███ ███ █████ █ █ ", + " ██ █ █ █ █ █ ███ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", + "███ ████ ████ ███ █ █ █ █ █ █ ███ ████ █ █ █ ", + " █ █ █ █ █ █ █████ █ █ █ █ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ████ █████ ████ █ █ █ █ ███ ████ █████ █ █ " +)) + val testFrameTemporaryBasalRatePercentage100Screen = makeDisplayFrame(arrayOf( "█████ ████ ████ █ █ █████ ████ █████ ", " █ █ █ █ █ █ █ █ █ █ █ ", @@ -1840,1403 +1980,1630 @@ val testFrameTbrDurationNoDurationScreen = makeDisplayFrame(arrayOf( " " )) -val testFrameTbrPercentageEnglishScreen = makeDisplayFrame(arrayOf( - "█████ ████ ████ ████ █████ ████ ███ █████ █ █ █████ █ ███ █████ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", - " █ ████ ████ ████ ████ ████ █ ████ █ █ █ █ █████ █ ███ ████ ", - " █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ █ █ █ █████ █ █ ███ █████ █ █ █ █ █ ████ █████ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) -val testFrameTbrDurationEnglishScreen = makeDisplayFrame(arrayOf( - "█████ ████ ████ ███ █ █ ████ █ █████ ███ ███ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", - " █ ████ ████ █ █ █ █ ████ █████ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ █ █ ███ ███ █ █ █ █ █ ███ ███ █ █ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) +object TbrPercentageAndDurationScreens { + val testFrameTbrPercentageEnglishScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ████ ████ █████ ████ ███ █████ █ █ █████ █ ███ █████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + " █ ████ ████ ████ ████ ████ █ ████ █ █ █ █ █████ █ ███ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ █████ █ █ ███ █████ █ █ █ █ █ ████ █████ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) + val testFrameTbrDurationEnglishScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ████ ███ █ █ ████ █ █████ ███ ███ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", + " █ ████ ████ █ █ █ █ ████ █████ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ ███ ███ █ █ █ █ █ ███ ███ █ █ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageSpanishScreen = makeDisplayFrame(arrayOf( - "████ ███ ████ ███ █████ █ █ █████ █ ███ █████ ███ ████ █████ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ ", - "████ █ █ ████ █ ████ █ █ █ █ █████ █ ████ █ █ ████ █ ", - "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ ███ █ █ ███ █████ █ █ █ █ █ ██ █████ ███ ████ █ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) -val testFrameTbrDurationSpanishScreen = makeDisplayFrame(arrayOf( - "███ █ █ ████ █ ███ ███ █ █ █ ███ █████ ███ ████ █████ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ ███ ██ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ ████ █████ █ █ █ █ █ █ █ █ █ ████ █ █ ████ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "███ ███ █ █ █ █ ███ ███ ███ █ █ ███ █████ ███ ████ █ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrPercentageSpanishScreen = makeDisplayFrame( + arrayOf( + "████ ███ ████ ███ █████ █ █ █████ █ ███ █████ ███ ████ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ ", + "████ █ █ ████ █ ████ █ █ █ █ █████ █ ████ █ █ ████ █ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ███ █ █ ███ █████ █ █ █ █ █ ██ █████ ███ ████ █ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) + val testFrameTbrDurationSpanishScreen = makeDisplayFrame( + arrayOf( + "███ █ █ ████ █ ███ ███ █ █ █ ███ █████ ███ ████ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ ███ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ ████ █████ █ █ █ █ █ █ █ █ █ ████ █ █ ████ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ███ █ █ █ █ ███ ███ ███ █ █ ███ █████ ███ ████ █ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageFrenchScreen = makeDisplayFrame(arrayOf( - "█ █ █ █ █████ █ █ ████ ███ █ █ ███ ████ █████ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █████ █ ████ █ █ ████ █ █ █ █ █ █ ████ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █████ █████ ███ █ █ ███ ███ ███ ████ █ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageFrenchScreen = makeDisplayFrame( + arrayOf( + "█ █ █ █ █████ █ █ ████ ███ █ █ ███ ████ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █████ █ ████ █ █ ████ █ █ █ █ █ █ ████ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █████ █████ ███ █ █ ███ ███ ███ ████ █ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationFrenchScreen = makeDisplayFrame(arrayOf( - "███ █ █ ████ █ █████ ███ █ █ ███ ████ █████ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █████ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ ████ █ ████ █ █ █ █ █ █ ████ █ ", - "█ █ █ █ █ █ ████ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "███ ███ █ █ █████ █████ ███ ███ ███ ████ █ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationFrenchScreen = makeDisplayFrame( + arrayOf( + "███ █ █ ████ █ █████ ███ █ █ ███ ████ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █████ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ ████ █ ████ █ █ █ █ █ █ ████ █ ", + "█ █ █ █ █ █ ████ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ███ █ █ █████ █████ ███ ███ ███ ████ █ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageItalianScreen = makeDisplayFrame(arrayOf( - "████ █████ ████ ███ █████ █ █ █████ █ █ █ █ █████ ████ ████ █████ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "████ ████ ████ █ ████ █ █ █ █ █ █ █████ █ ████ ████ ████ █ ", - "█ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █████ █ █ ███ █████ █ █ █ ███ █ █ █████ █████ █ ████ █ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageItalianScreen = makeDisplayFrame( + arrayOf( + "████ █████ ████ ███ █████ █ █ █████ █ █ █ █ █████ ████ ████ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "████ ████ ████ █ ████ █ █ █ █ █ █ █████ █ ████ ████ ████ █ ", + "█ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █████ █ █ ███ █████ █ █ █ ███ █ █ █████ █████ █ ████ █ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationItalianScreen = makeDisplayFrame(arrayOf( - "███ █ █ ████ █ █████ █ ████ ████ █████ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ ████ █████ █ █████ ████ ████ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "███ ███ █ █ █ █ █ █ █ █ ████ █ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationItalianScreen = makeDisplayFrame( + arrayOf( + "███ █ █ ████ █ █████ █ ████ ████ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ ████ █████ █ █████ ████ ████ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ███ █ █ █ █ █ █ █ █ ████ █ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageRussianScreen = makeDisplayFrame(arrayOf( - "█████ ████ ███ █ █ █████ █ █ █████ ████ █████ ███ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ ████ █ █ █ █ ████ █████ █ ████ ████ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █████ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ ███ █ █████ █ █ █ ████ ████ ███ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageRussianScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ███ █ █ █████ █ █ █████ ████ █████ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ █ █ █ ████ █████ █ ████ ████ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █████ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ ███ █ █████ █ █ █ ████ ████ ███ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationRussianScreen = makeDisplayFrame(arrayOf( - "█████ ████ ███ ██ ███ ████ █ █ █ █ █ █████ ████ █████ ███ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ ███ █ █ █ █ █ █ █ █ ", - "█ █ ████ █ █ █ █ █ █ █ █ ███ █ █ █ █ ████ ████ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █████ █ █ █ █ █ █ █ ██ █ █ ██ █ █ █ █ █ █ ", - "█ █ █ ███ █ █ ███ ██ █ █ █ █ █ █ █ ██ ████ ████ ███ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationRussianScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ███ ██ ███ ████ █ █ █ █ █ █████ ████ █████ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ ███ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ █ █ █ █ █ █ █ ███ █ █ █ █ ████ ████ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █████ █ █ █ █ █ █ █ ██ █ █ ██ █ █ █ █ █ █ ", + "█ █ █ ███ █ █ ███ ██ █ █ █ █ █ █ █ ██ ████ ████ ███ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageTurkishScreen = makeDisplayFrame(arrayOf( - " ███ ████ █ █ █ █ █ █ █████ ███ █████ ████ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ███ ", - "█ ███ ████ █████ █ █ █ █ █ █ █ ████ ███ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " ████ ████ █ █ █ ███ █████ ███ █████ ████ ███ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageTurkishScreen = makeDisplayFrame( + arrayOf( + " ███ ████ █ █ █ █ █ █ █████ ███ █████ ████ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ███ ", + "█ ███ ████ █████ █ █ █ █ █ █ █ ████ ███ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ████ ████ █ █ █ ███ █████ ███ █████ ████ ███ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationTurkishScreen = makeDisplayFrame(arrayOf( - " ███ ████ █ █ ████ █ █ ████ █████ ████ █ ", - "█ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ ███ ", - "█ ███ ████ █████ ███ █ █ ████ ████ ███ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " ████ ████ █ █ ████ ███ █ █ █████ ████ ███ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationTurkishScreen = makeDisplayFrame( + arrayOf( + " ███ ████ █ █ ████ █ █ ████ █████ ████ █ ", + "█ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ ███ ", + "█ ███ ████ █████ ███ █ █ ████ ████ ███ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ████ ████ █ █ ████ ███ █ █ █████ ████ ███ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentagePolishScreen = makeDisplayFrame(arrayOf( - "████ ████ ███ ███ █████ █ █ █████ █████ ███ ████ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", - "████ ████ █ █ █ ████ █ █ █ █ █ █ █ ████ ", - "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ ███ ███ █████ █ █ █ █ ███ █ ", - " ", - " ██ ████ ████ ██ ██ ", - " ███████ ███ ██ ██ ██ ██ ████ ██ ", - " ███████ ████ ██ ██ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ████ ████ ", - " ", - " ", - " ", - " ", - " ", - " ", - " ", - " ", - " " -)) + val testFrameTbrPercentagePolishScreen = makeDisplayFrame( + arrayOf( + "████ ████ ███ ███ █████ █ █ █████ █████ ███ ████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", + "████ ████ █ █ █ ████ █ █ █ █ █ █ █ ████ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ ███ ███ █████ █ █ █ █ ███ █ ", + " ", + " ██ ████ ████ ██ ██ ", + " ███████ ███ ██ ██ ██ ██ ████ ██ ", + " ███████ ████ ██ ██ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ████ ████ ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " " + ) + ) -val testFrameTbrDurationPolishScreen = makeDisplayFrame(arrayOf( - " ███ █████ █ ████ █████ ████ █ █ █ █ █ ███ █ █████ ███ ████ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", - "█ █ █████ ███ █ ████ █ █ █ █████ █ █ █ █ █████ █ █ █ ████ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " ███ █████ █ █ ████ █ █ █ █ █ █ █ █ █ ███ █ █ █ ███ █ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationPolishScreen = makeDisplayFrame( + arrayOf( + " ███ █████ █ ████ █████ ████ █ █ █ █ █ ███ █ █████ ███ ████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ █████ ███ █ ████ █ █ █ █████ █ █ █ █ █████ █ █ █ ████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ███ █████ █ █ ████ █ █ █ █ █ █ █ █ █ ███ █ █ █ ███ █ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageCzechScreen = makeDisplayFrame(arrayOf( - "████ ████ ███ ███ █████ █ █ █████ ███ ███ ████ ███ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ ", - "████ ████ █ █ █ ████ █ █ █ █ █ █ █ █ ████ █ █ ", - "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ ███ ███ █████ █ █ █ ███ ███ ████ ███ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageCzechScreen = makeDisplayFrame( + arrayOf( + "████ ████ ███ ███ █████ █ █ █████ ███ ███ ████ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ ", + "████ ████ █ █ █ ████ █ █ █ █ █ █ █ █ ████ █ █ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ ███ ███ █████ █ █ █ ███ ███ ████ ███ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationCzechScreen = makeDisplayFrame(arrayOf( - "█████ ████ █ █ █ █ █ █ ███ ████ ███ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ ██ █ ███ █ █ █ █ █ █ ", - " █ ████ █ █ █ █ █ █ █ █ █ █ ████ █ █ ", - " █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █████ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ ███ ███ ████ ███ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationCzechScreen = makeDisplayFrame( + arrayOf( + "█████ ████ █ █ █ █ █ █ ███ ████ ███ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ ██ █ ███ █ █ █ █ █ █ ", + " █ ████ █ █ █ █ █ █ █ █ █ █ ████ █ █ ", + " █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █████ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ ███ ███ ████ ███ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageHungarianScreen = makeDisplayFrame(arrayOf( - "█████ ████ ████ ████ █████ █ █████ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █████ █ █ ", - " █ ████ ████ ███ █ █ █ █ █████ █ █ ██ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ ████ █ █ ", - " █ █ █ █ █ █ █ █████ █ █ █ █ █ █ █ ", - " █ ████ █ █ ████ █████ █ █ █████ █ █ █████ █████ █ █ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageHungarianScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ████ ████ █████ █ █████ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █████ █ █ ", + " █ ████ ████ ███ █ █ █ █ █████ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ████ █ █ ", + " █ █ █ █ █ █ █ █████ █ █ █ █ █ █ █ ", + " █ ████ █ █ ████ █████ █ █ █████ █ █ █████ █████ █ █ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationHungarianScreen = makeDisplayFrame(arrayOf( - "█████ ████ ████ ███ ███ █ █ █████ █ ████ █████ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ██ ", - " █ █ █ █ █ █ █ █ ███ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ ████ █ █ █ █ █ █ █████ ████ █ █████ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ █ █ ███ ███ ███ █ █ █ █ █ █ █ █ █ █ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationHungarianScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ████ ███ ███ █ █ █████ █ ████ █████ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ██ ", + " █ █ █ █ █ █ █ █ ███ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ████ █ █ █ █ █ █ █████ ████ █ █████ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ ███ ███ ███ █ █ █ █ █ █ █ █ █ █ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageSlovakScreen = makeDisplayFrame(arrayOf( - "████ █████ ████ ███ █████ █ █ █████ ███ ███ ████ ███ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ ", - "████ ████ ████ █ ████ █ █ █ █ █ █ █ █ ████ █ █ ", - "█ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █████ █ █ ███ █████ █ █ █ ███ ███ ████ ███ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageSlovakScreen = makeDisplayFrame( + arrayOf( + "████ █████ ████ ███ █████ █ █ █████ ███ ███ ████ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ ", + "████ ████ ████ █ ████ █ █ █ █ █ █ █ █ ████ █ █ ", + "█ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █████ █ █ ███ █████ █ █ █ ███ ███ ████ ███ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationSlovakScreen = makeDisplayFrame(arrayOf( - "█████ ████ █ █ █ █ █ ███ █████ ███ ████ ███ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", - " █ ████ █ █ █████ █ █ █ █ ████ █ █ ████ █ █ ", - " █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ ███ █████ ███ ████ ███ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationSlovakScreen = makeDisplayFrame( + arrayOf( + "█████ ████ █ █ █ █ █ ███ █████ ███ ████ ███ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █████ █ █ █ █ ████ █ █ ████ █ █ ", + " █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ ███ █████ ███ ████ ███ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageRomanianScreen = makeDisplayFrame(arrayOf( - "████ ████ ███ ███ █████ █ █ █████ ████ ████ █████ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", - "████ ████ █ █ █ ████ █ █ █ █ ████ ████ █ ", - "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ ███ ███ █████ █ █ █ █ █ ████ █ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageRomanianScreen = makeDisplayFrame( + arrayOf( + "████ ████ ███ ███ █████ █ █ █████ ████ ████ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", + "████ ████ █ █ █ ████ █ █ █ █ ████ ████ █ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ ███ ███ █████ █ █ █ █ █ ████ █ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationRomanianScreen = makeDisplayFrame(arrayOf( - "███ █ █ ████ █ █████ █ ████ ████ █████ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ ████ █████ █ █████ ████ ████ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "███ ███ █ █ █ █ █ █ █ █ █ ████ █ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationRomanianScreen = makeDisplayFrame( + arrayOf( + "███ █ █ ████ █ █████ █ ████ ████ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ ████ █████ █ █████ ████ ████ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ███ █ █ █ █ █ █ █ █ █ ████ █ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageCroatianScreen = makeDisplayFrame(arrayOf( - "████ ███ ████ █████ ███ █████ █ █ █ ████ ████ ███ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ███ ", - "████ █ █ ███ █ █ █ █ █████ ██ ████ ████ █ █ █████ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ████ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ ███ ████ █ ███ █ █ █ █ █ █ ████ ███ ████ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageCroatianScreen = makeDisplayFrame( + arrayOf( + "████ ███ ████ █████ ███ █████ █ █ █ ████ ████ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ███ ", + "████ █ █ ███ █ █ █ █ █████ ██ ████ ████ █ █ █████ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ███ ████ █ ███ █ █ █ █ █ █ ████ ███ ████ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationCroatianScreen = makeDisplayFrame(arrayOf( - "█████ ████ █ ███ █ █ █ ███ █████ ████ ████ ███ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ███ ", - " █ ████ █████ █ █████ █ █ █ █ ████ ████ ████ █ █ █████ █ ", - " █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ████ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ ██ █ █ █ █ ██ █████ █ ████ ███ ████ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationCroatianScreen = makeDisplayFrame( + arrayOf( + "█████ ████ █ ███ █ █ █ ███ █████ ████ ████ ███ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ███ ", + " █ ████ █████ █ █████ █ █ █ █ ████ ████ ████ █ █ █████ █ ", + " █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ ██ █ █ █ █ ██ █████ █ ████ ███ ████ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageDutchScreen = makeDisplayFrame(arrayOf( - "█████ ████ ███ ████ █████ ████ ███ █████ █ █ █████ █ ███ █████ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", - " █ ████ █ █ █████ ████ ████ ████ █ ████ █ █ █ █ █████ █ ███ ████ ", - " █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ ███ █ █████ █ █ ███ █████ █ █ █ █ █ ████ █████ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageDutchScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ███ ████ █████ ████ ███ █████ █ █ █████ █ ███ █████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + " █ ████ █ █ █████ ████ ████ ████ █ ████ █ █ █ █ █████ █ ███ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ███ █ █████ █ █ ███ █████ █ █ █ █ █ ████ █████ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationDutchScreen = makeDisplayFrame(arrayOf( - "█████ ████ ███ ███ █ █ █ █ ████ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ █ █ █████ █ █ █ █ █ █ ████ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ ███ ███ ███ ███ █ █ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationDutchScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ███ ███ █ █ █ █ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █████ █ █ █ █ █ █ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ███ ███ ███ ███ █ █ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageGreekScreen = makeDisplayFrame(arrayOf( - "█████ ███ █████ ███ █████ █████ ███ █████ ████ ████ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ ████ ████ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ██ █ ██ ", - "█ █ ███ █████ ███ █████ █ ███ █ █ ██ ████ ██ █ ██ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageGreekScreen = makeDisplayFrame( + arrayOf( + "█████ ███ █████ ███ █████ █████ ███ █████ ████ ████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ ████ ████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ██ █ ██ ", + "█ █ ███ █████ ███ █████ █ ███ █ █ ██ ████ ██ █ ██ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationGreekScreen = makeDisplayFrame(arrayOf( - " █ ███ █ ████ █ █ █████ ███ █ █████ ████ ████ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ ████ ██ ████ █ █ █ █ █ ████ ████ ", - "█ █ █ █████ █ █ █ █ █ █████ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ██ █ ██ ", - "█████ ███ █ █ █ █ █ █████ ███ █ █ █ █ ██ ████ ██ █ ██ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationGreekScreen = makeDisplayFrame( + arrayOf( + " █ ███ █ ████ █ █ █████ ███ █ █████ ████ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ ████ ██ ████ █ █ █ █ █ ████ ████ ", + "█ █ █ █████ █ █ █ █ █ █████ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ██ █ ██ ", + "█████ ███ █ █ █ █ █ █████ ███ █ █ █ █ ██ ████ ██ █ ██ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageFinnishScreen = makeDisplayFrame(arrayOf( - "█████ ████ █ ████ ████ ███ ████ █████ █ █ █████ █████ ███ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ ", - " █ ████ █████ █████ ████ ████ █ █ ███ ████ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ █ █ █ █ █ ███ ████ █████ █ █ █ █ ███ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageFinnishScreen = makeDisplayFrame( + arrayOf( + "█████ ████ █ ████ ████ ███ ████ █████ █ █ █████ █████ ███ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ ", + " █ ████ █████ █████ ████ ████ █ █ ███ ████ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ █ █ ███ ████ █████ █ █ █ █ ███ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationFinnishScreen = makeDisplayFrame(arrayOf( - "█████ ████ █ █ █ █████ ████ █████ ███ ", - " █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ █████ █████ ██ ████ ███ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ █ █ █ █ █████ ████ █ ███ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationFinnishScreen = makeDisplayFrame( + arrayOf( + "█████ ████ █ █ █ █████ ████ █████ ███ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █████ █████ ██ ████ ███ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ █ █████ ████ █ ███ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageNorwegianScreen = makeDisplayFrame(arrayOf( - "█ █ ████ ███ ████ ████ ███ ████ █████ █ █ █████ ", - "██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", - "█ █ █ ████ █ █ █████ ████ ████ █ █ ███ ████ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ ████ ███ █ █ █ ███ ████ █████ █ █ █ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageNorwegianScreen = makeDisplayFrame( + arrayOf( + "█ █ ████ ███ ████ ████ ███ ████ █████ █ █ █████ ", + "██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + "█ █ █ ████ █ █ █████ ████ ████ █ █ ███ ████ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ ███ █ █ █ ███ ████ █████ █ █ █ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationNorwegianScreen = makeDisplayFrame(arrayOf( - "█ █ ████ ███ █ █ █ ████ ███ ███ █ █ █████ █████ ", - "██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ ████ █ █ █████ █ █ █████ ████ █ █ ███ █████ ████ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ ████ ███ █ █ █ █ █ ███ ████ █ █ █████ █ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationNorwegianScreen = makeDisplayFrame( + arrayOf( + "█ █ ████ ███ █ █ █ ████ ███ ███ █ █ █████ █████ ", + "██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ ████ █ █ █████ █ █ █████ ████ █ █ ███ █████ ████ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ ███ █ █ █ █ █ ███ ████ █ █ █████ █ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentagePortugueseScreen = makeDisplayFrame(arrayOf( - "███ ████ █████ ████ █████ ████ ███ █████ █ █ █████ █ ███ █████ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ██ ", - "█ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", - "█ █ ████ █ ████ ████ ████ █ ████ █ █ █ █ █████ █ ███ ████ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "███ ████ █ █ █████ █ █ ███ █████ █ █ █ █ █ ████ █████ █ █ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentagePortugueseScreen = makeDisplayFrame( + arrayOf( + "███ ████ █████ ████ █████ ████ ███ █████ █ █ █████ █ ███ █████ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ██ ", + "█ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ ████ ████ ████ █ ████ █ █ █ █ █████ █ ███ ████ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ █ █████ █ █ ███ █████ █ █ █ █ █ ████ █████ █ █ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationPortugueseScreen = makeDisplayFrame(arrayOf( - "███ ████ █████ ███ █ █ ████ █ ████ █ █ ███ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ ████ █ █ █ █ █ ████ █████ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ ████ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █████ █ █ ", - "███ ████ █ ███ ███ █ █ █ █ ██ █ █ ███ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationPortugueseScreen = makeDisplayFrame( + arrayOf( + "███ ████ █████ ███ █ █ ████ █ ████ █ █ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ █ █ █ █ ████ █████ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ ████ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █████ █ █ ", + "███ ████ █ ███ ███ █ █ █ █ ██ █ █ ███ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageSwedishScreen = makeDisplayFrame(arrayOf( - "█████ ████ ███ ████ ████ ███ ███ █████ █ █ █████ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", - " █ ████ █ █ ████ ████ █ █ █ ████ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ ███ █ █ █ ███ ███ █████ █ █ █ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageSwedishScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ███ ████ ████ ███ ███ █████ █ █ █████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + " █ ████ █ █ ████ ████ █ █ █ ████ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ███ █ █ █ ███ ███ █████ █ █ █ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationSwedishScreen = makeDisplayFrame(arrayOf( - "█████ ████ ███ ███ █ █ ████ █ █████ ███ ███ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", - " █ ████ █ █ █ █ █ █ ████ █████ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ ███ ███ ███ █ █ █ █ █ ███ ███ █ █ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationSwedishScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ███ ███ █ █ ████ █ █████ ███ ███ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", + " █ ████ █ █ █ █ █ █ ████ █████ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ███ ███ ███ █ █ █ █ █ ███ ███ █ █ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageDanishScreen = makeDisplayFrame(arrayOf( - "█ █ ████ ████ ████ ████ ███ ███ █████ █ █ █████ ", - "██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", - "█ █ █ ████ ████ █████ ████ ████ █ █ █ ████ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ ████ █ █ █ █ █ ███ ███ █████ █ █ █ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageDanishScreen = makeDisplayFrame( + arrayOf( + "█ █ ████ ████ ████ ████ ███ ███ █████ █ █ █████ ", + "██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + "█ █ █ ████ ████ █████ ████ ████ █ █ █ ████ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ █ █ █ █ ███ ███ █████ █ █ █ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationDanishScreen = makeDisplayFrame(arrayOf( - "█ █ ████ ████ █ █ █ ████ ███ ███ █ █ █████ ███ ", - "██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ ████ ████ █████ █ █ █████ ████ █ █ ███ █████ ████ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - "█ █ ████ █ █ █ █ █ █ █ ███ ████ █ █ █████ ███ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationDanishScreen = makeDisplayFrame( + arrayOf( + "█ █ ████ ████ █ █ █ ████ ███ ███ █ █ █████ ███ ", + "██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ ████ ████ █████ █ █ █████ ████ █ █ ███ █████ ████ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ █ █ █ █ █ █ ███ ████ █ █ █████ ███ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) -val testFrameTbrPercentageGermanScreen = makeDisplayFrame(arrayOf( - "█████ ████ ████ █ █ █████ ████ █████ ", - " █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ ████ █ █ █ ████ ████ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ █ █ █ █ █████ █ █ █ ", - " ", - " ██ ██ ████ ██ ██ ", - " ███████ ███ ███ ██ ██ ████ ██ ", - " ███████ ████ ████ ██ ██ ████ ██ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - " ██ ███████ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "███████ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", - "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - "██ ██ ██ ██ ██ ██ ████ ", - " ", - " █ ███ ███ █████ ███ ", - " ██ █ █ █ █ ██ █ █ █ ", - "███████ █ ██ █ ██ ██ █ █ ██ ", - "████████ █ █ █ █ █ █ █ █ █ █ ", - "███████ ██ █ ██ █ ██ █ ██ █ ", - " ██ █ █ █ █ ██ █ █ █ █ ", - " █ ███ ███ ███ ███ ", - " " -)) + val testFrameTbrPercentageGermanScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ████ █ █ █████ ████ █████ ", + " █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ████ █ █ █ ████ ████ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ █ █████ █ █ █ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █████ ███ ", + " ██ █ █ █ █ ██ █ █ █ ", + "███████ █ ██ █ ██ ██ █ █ ██ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ ██ █ ", + " ██ █ █ █ █ ██ █ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) -val testFrameTbrDurationGermanScreen = makeDisplayFrame(arrayOf( - "█████ ████ ████ ███ █ █ █ █████ ████ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ ████ █ █ █████ █ █ ████ ████ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", - " █ ████ █ █ ███ █ █ ███ █████ █ █ ", - " ", - " ████ ████ █████ ████ ", - " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", - "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", - "████████████████ ██ ██ ██ ██ ██ ██ ██ ", - "███████████████ ██ ██ ██ ██ ██ ██ ██ ", - " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", - " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", - " ██ ████ ████ █████ ████ ", - " ", - " ████ █ █ ███ ██ ", - " █ ███ ██ ██ █ █ ██ █ ", - "███ █ █ █ █ █ ██ █ ", - "█ █ █ █ █ █ █ █ █ █ ", - "█ █ █ █ █ █ ██ █ █ ", - "█ █ █ █ █ █ █ █ █ ██ ", - "█ █ █ █ ███ ███ ███ ██ ", - " " -)) + val testFrameTbrDurationGermanScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ████ ███ █ █ █ █████ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ████ █ █ █████ █ █ ████ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ ███ █ █ ███ █████ █ █ ", + " ", + " ████ ████ █████ ████ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ███ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ █████ ████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) + + val testFrameTbrPercentageSlovenianScreen = makeDisplayFrame( + arrayOf( + " ███ ███ ████ █████ ███ █████ █████ █ █ █████ ████ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ ███ █ █ █ █ ████ ██ █ ████ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ███ ███ ████ █ ███ █ █████ █ █ █████ ████ ███ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █ █████ ", + " ██ █ █ █ █ ██ ██ █ ", + "███████ █ ██ █ ██ ██ █ ████ ", + "████████ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ █ ", + " ██ █ █ █ █ ██ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + ) + ) + + val testFrameTbrDurationSlovenianScreen = makeDisplayFrame( + arrayOf( + "█████ ████ █ ███ █ █ █ ███ █████ █████ ████ ███ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", + " █ ████ █████ █ █████ █ █ █ █ ████ █ ████ █ █ ", + " █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ ██ █ █ █ █ ██ █████ █████ ████ ███ ", + " ", + " ████ ████ ██ ███████ ", + " ██ ██ ██ ██ ██ ███ ██ ", + " ███ ██ ██ ██ ██ ████ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██████ ", + "███████████████ ██ ██ ██ ██ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ ██ █████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + ) + ) + + val testFrameTbrPercentageLithuanianScreen = makeDisplayFrame(arrayOf( + "█████ ████ ████ ████ █████ ███ █ █ █ █ █ █ █ ████ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ██ ██ █ ██ █ ", + " █ █ █ █ █ █ █ █ █ █ █ ████ █ █ █ █████ █ █ ", + " █ ████ ████ ████ ████ █ ██ █ █ █ █ █ ███ █ ", + " █ █ █ █ █ █ █ █ █ █ █ ███ █ █ ████ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ", + " █ ████ █ █ █ █ █████ ███ █ █ ████ █ █ █████ ████ ██ ", + " ", + " ██ ██ ████ ██ ██ ", + " ███████ ███ ███ ██ ██ ████ ██ ", + " ███████ ████ ████ ██ ██ ████ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + " ██ ███████ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "███████ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██ ██ ██ ██ ██ ██ ████ ", + " ", + " █ ███ ███ █ █████ ", + " ██ █ █ █ █ ██ ██ █ ", + "███████ █ ██ █ ██ ██ █ ████ ", + "████████ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █ █ ", + " ██ █ █ █ █ ██ █ █ █ ", + " █ ███ ███ ███ ███ ", + " " + )) + + val testFrameTbrDurationLithuanianScreen = makeDisplayFrame(arrayOf( + "█████ ████ ████ █████ ████ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ██ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █████ ", + " █ ████ ████ █ ████ █ █ ██ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ █ █ ███ █ █ █ █ █████ ", + " ", + " ████ ████ ██ ███████ ", + " ██ ██ ██ ██ ██ ███ ██ ", + " ███ ██ ██ ██ ██ ████ ██ ", + " ████ ██ ██ ██ ██ ██ ██ ", + " █████ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██████ ", + "███████████████ ██ ██ ██ ██ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ███ ██ ██ ", + "████████████████ ██ ██ ██ ██ ██ ██ ", + "███████████████ ██ ██ ██ ██ ██ ██ ", + " ██████ ██ ██ ██ ██ ███ ██ ██ ", + " █████ ██ ██ ██ ██ ███ ██ ██ ", + " ████ ██ ██ ██ ██ ███ ██ ██ ", + " ███ ██ ██ ██ ██ ██ ██ ██ ", + " ██ ████ ████ ██ █████ ", + " ", + " ████ █ █ ███ ██ ", + " █ ███ ██ ██ █ █ ██ █ ", + "███ █ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ ", + "█ █ █ █ ███ ███ ███ ██ ", + " " + )) + +} val testTimeAndDateSettingsHour12hFormatScreen = makeDisplayFrame(arrayOf( "█ █ ███ █ █ ████ ", @@ -6808,6 +7175,356 @@ val testTimeAndDateSettingsDayGermanScreen = makeDisplayFrame(arrayOf( " " )) +val testTimeAndDateSettingsHourSlovenianScreen = makeDisplayFrame(arrayOf( + "█ █ ████ █ ", + "█ █ █ █ █ █ ", + "█ █ █ █ █ █ ", + "█ █ ████ █████ ", + "█ █ █ █ █ █ ", + "█ █ █ █ █ █ ", + " ███ █ █ █ █ ", + " ", + " ██ ████ ", + " █████ ███ ██ ██ ", + " ██ ██ ████ ██ ██ ", + " █ █ █ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + "█ █ █ ██ ██ ██ ", + "█ █ ██ ██ ██ ███ ", + "█ ████ ██ ██ ██████ ", + "█ ██ ██ ██ ", + "█ ██ ██ ██ ", + " █ ███ ██ ██ ", + " █ ██ ██ ██ ", + " ██ ████ ██ ██ ", + " █████████ ██ ██ ", + " █████ ██ ███ ", + " ", + " █ ███ █████ ███ ███ ███ ███ █████ ███ █████", + " ██ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ ██ ████ █ ██ █ ██ █ █ █ ██ █ █ █ ", + " █ ████ █ █ █ █ █ █ █ ███ █ █ █ █ █ █ ", + " █ █ ██ █ ██ █ ██ █ █ █ ██ █ █ █ █", + " █ █ ██ █ █ █ █ █ █ █ █ ██ █ █ █ █ ██ █ █ █", + " ███ ██ ███ ███ ███ ███ ██ ███ ███ ██ █████ ███ ", + " " +)) + +val testTimeAndDateSettingsMinuteSlovenianScreen = makeDisplayFrame(arrayOf( + "█ █ ███ █ █ █ █ █████ █████ ", + "██ ██ █ █ █ █ █ █ █ ", + "█ █ █ █ ██ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ ████ ", + "█ █ █ █ ██ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ ", + "█ █ ███ █ █ ███ █ █████ ", + " ", + " ███████ ████ ", + " █████ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ █ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + "█ █ █ ██████ ██ ██ ", + "█ █ ██ ██ ██ ██ ", + "█ ████ ██ ██ ██ ██ ", + "█ ██ ██ ██ ██ ", + "█ ██ ██ ██ ██ ", + " █ ███ ██ ██ ██ ", + " █ ██ ██ ██ ██ ", + " ██ ████ ██ ██ ██ ", + " █████████ ██ ██ ██ ██ ", + " █████ █████ ████ ", + " ", + " █ ███ █████ ███ ███ ███ ███ █████ ███ █████", + " ██ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ ██ ████ █ ██ █ ██ █ █ █ ██ █ █ █ ", + " █ ████ █ █ █ █ █ █ █ ███ █ █ █ █ █ █ ", + " █ █ ██ █ ██ █ ██ █ █ █ ██ █ █ █ █", + " █ █ ██ █ █ █ █ █ █ █ █ ██ █ █ █ █ ██ █ █ █", + " ███ ██ ███ ███ ███ ███ ██ ███ ███ ██ █████ ███ ", + " " +)) + +val testTimeAndDateSettingsYearSlovenianScreen = makeDisplayFrame(arrayOf( + "█ █████ █████ ███ ", + "█ █ █ █ █ ", + "█ █ █ █ █ ", + "█ ████ █ █ █ ", + "█ █ █ █ █ ", + "█ █ █ █ █ ", + "█████ █████ █ ███ ", + " ", + " ████ ████ ████ █████ ", + "█████████████ ██ ██ ██ ██ ██ ██ ██ ██ ", + "█ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██████████████ ██ ██ ██ ██ ██ ██ ██ ", + "█ █ █ █ █ █ ██ ██ ██ ██ ██ ██ ", + "██████████████ ██ ██ ██ ██ ██ ", + "█ █ █ █ █ █ ██ ██ ██ ██ ██ ███ ", + "██████████████ ██ ██ ██ ██ ██ ", + "█ █ █ █ █ █ ██ ██ ██ ██ ██ ██ ", + "██████████████ ██ ██ ██ ██ ██ ", + "█ █ █ █ ██████ ██ ██ ██ ██ ██ ", + "██████████████ ██ ██ ██ ██ ██ ", + " █████████████ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ", + " ████████ ████ ████████ █████ ", + " ", + " █ ███ █████ ███ ███ ███ ███ █████ ███ █████", + " ██ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ ██ ████ █ ██ █ ██ █ █ █ ██ █ █ █ ", + " █ ████ █ █ █ █ █ █ █ ███ █ █ █ █ █ █ ", + " █ █ ██ █ ██ █ ██ █ █ █ ██ █ █ █ █", + " █ █ ██ █ █ █ █ █ █ █ █ ██ █ █ █ █ ██ █ █ █", + " ███ ██ ███ ███ ███ ███ ██ ███ ███ ██ █████ ███ ", + " " +)) + +val testTimeAndDateSettingsMonthSlovenianScreen = makeDisplayFrame(arrayOf( + "█ █ █████ ████ █████ ███ ", + "██ ██ █ █ █ █ █ ", + "█ █ █ █ █ █ █ ", + "█ █ █ ████ ███ ████ █ ", + "█ █ █ █ █ █ ", + "█ █ █ █ █ █ █ ", + "█ █ █████ ████ █████ ███ ", + " ", + " █████ ", + "█████████████ ██ ██ ", + "█ ██ ██ ", + "██████████████ ██ ", + "█ █ █ █ █ █ ██ ██ ", + "██████████████ ██ ", + "█ █ █ █ █ █ ██ ███ ", + "██████████████ ██ ", + "█ █ █ █ █ █ ██ ██ ", + "██████████████ ██ ", + "█ █ █ █ ██████ ██ ", + "██████████████ ██ ", + " █████████████ ██ ", + " ██ ██ ", + " █████ ", + " ", + " █ ███ █████ ███ ███ ███ ███ █████ ███ █████", + " ██ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ ██ ████ █ ██ █ ██ █ █ █ ██ █ █ █ ", + " █ ████ █ █ █ █ █ █ █ ███ █ █ █ █ █ █ ", + " █ █ ██ █ ██ █ ██ █ █ █ ██ █ █ █ █", + " █ █ ██ █ █ █ █ █ █ █ █ ██ █ █ █ █ ██ █ █ █", + " ███ ██ ███ ███ ███ ███ ██ ███ ███ ██ █████ ███ ", + " " +)) + +val testTimeAndDateSettingsDaySlovenianScreen = makeDisplayFrame(arrayOf( + "███ █ █ █ ", + "█ █ █ █ █ █ ", + "█ █ █ █ ██ █ ", + "█ █ █████ █ █ █ ", + "█ █ █ █ █ ██ ", + "█ █ █ █ █ █ ", + "███ █ █ █ █ ", + " ", + " ████ ", + "█████████████ ██ ██ ", + "█ ██ ██ ██ ", + "██████████████ ██ ██ ", + "█ █ █ █ █ █ ██ ██ ██ ", + "██████████████ ██ ██ ", + "█ █ █ █ █ █ ██ ████ ", + "██████████████ ██ ██ ", + "█ █ █ █ █ █ ██ ██ ██ ", + "██████████████ ██ ██ ", + "█ █ █ █ ██████ ██ ██ ", + "██████████████ ██ ██ ", + " █████████████ ██ ██ ", + " ██ ██ ", + " ████ ", + " ", + " █ ███ █████ ███ ███ ███ ███ █████ ███ █████", + " ██ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ ██ ████ █ ██ █ ██ █ █ █ ██ █ █ █ ", + " █ ████ █ █ █ █ █ █ █ ███ █ █ █ █ █ █ ", + " █ █ ██ █ ██ █ ██ █ █ █ ██ █ █ █ █", + " █ █ ██ █ █ █ █ █ █ █ █ ██ █ █ █ █ ██ █ █ █", + " ███ ██ ███ ███ ███ ███ ██ ███ ███ ██ █████ ███ ", + " " +)) + +val testTimeAndDateSettingsHourLithuanianScreen = makeDisplayFrame(arrayOf( + "█ █ █ █ █ █ █ ███ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ ██ █ █ █ █ █ ", + "█ █ █████ █ █████ █ █ █ █ █ █████ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █████ █ █ █ █ ███ █ █ ", + " ", + " ████ ████ ", + " █████ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ", + " █ █ █ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + "█ █ █ ██ ██ ██ ", + "█ █ ██ ██ ██ ██ ", + "█ ████ ██ ██ ██ ██ ", + "█ ██ ██ ██ ██ ", + "█ ██ ██ ██ ██ ", + " █ ███ ██ ██ ██ ", + " █ ██ ██ ██ ██ ", + " ██ ████ ██ ██ ██ ", + " █████████ ██ ██ ██ ", + " █████ ████████ ████ ", + " ", + " ███ ███ █ ██ ███ ███ ███ █████ ███ █████", + "█ █ █ █ ██ ██ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ ██ ██ █ █ █ ██ █ █ █ ██ █ █ █ ", + " █ █ █ █ █ ████ █ █ █ ███ █ █ █ █ █ █ ", + " █ ██ █ ██ █ █ █ ██ █ █ █ ██ █ █ █ █", + " █ █ █ ██ █ █ █ █ █ █ █ ██ █ █ █ █ ██ █ █ █", + "█████ ███ ███ ███ ███ ███ ██ ███ ███ ██ █████ ███ ", + " " +)) + +val testTimeAndDateSettingsMinuteLithuanianScreen = makeDisplayFrame(arrayOf( + "█ █ ███ █ █ █ █ █████ █ ", + "██ ██ █ █ █ █ █ █ ", + "█ █ █ █ ██ █ █ █ █ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ ██ █ █ █ ████ ", + "█ █ █ █ █ █ █ █ █ ", + "█ █ ███ █ █ ███ █ █████ ", + " ", + " ██ ███ ", + " █████ ███ ██ ", + " ██ ██ ████ ██ ", + " █ █ █ ██ ██ ", + " █ █ ██ ██ ██ ", + "█ █ █ ██ ██ ", + "█ █ ██ ██ ██████ ", + "█ ████ ██ ██ ███ ██ ", + "█ ██ ██ ██ ██ ", + "█ ██ ██ ██ ██ ", + " █ ███ ██ ██ ██ ", + " █ ██ ██ ██ ██ ", + " ██ ████ ██ ██ ██ ", + " █████████ ██ ██ ██ ", + " █████ ██ ████ ", + " ", + " ███ ███ █ ██ ███ ███ ███ █████ ███ █████", + "█ █ █ █ ██ ██ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ ██ ██ █ █ █ ██ █ █ █ ██ █ █ █ ", + " █ █ █ █ █ ████ █ █ █ ███ █ █ █ █ █ █ ", + " █ ██ █ ██ █ █ █ ██ █ █ █ ██ █ █ █ █", + " █ █ █ ██ █ █ █ █ █ █ █ ██ █ █ █ █ ██ █ █ █", + "█████ ███ ███ ███ ███ ███ ██ ███ ███ ██ █████ ███ ", + " " +)) + +val testTimeAndDateSettingsYearLithuanianScreen = makeDisplayFrame(arrayOf( + "█ █ █████ █████ █ ███ ", + "██ ██ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ ", + "█ █ █ ████ █ █████ █ ", + "█ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ ", + "█ █ █████ █ █ █ ███ ", + " ", + " ████ ████ ████ █████ ", + "█████████████ ██ ██ ██ ██ ██ ██ ██ ██ ", + "█ ██ ██ ██ ██ ██ ██ ██ ██ ", + "██████████████ ██ ██ ██ ██ ██ ██ ██ ", + "█ █ █ █ █ █ ██ ██ ██ ██ ██ ██ ", + "██████████████ ██ ██ ██ ██ ██ ", + "█ █ █ █ █ █ ██ ██ ██ ██ ██ ███ ", + "██████████████ ██ ██ ██ ██ ██ ", + "█ █ █ █ █ █ ██ ██ ██ ██ ██ ██ ", + "██████████████ ██ ██ ██ ██ ██ ", + "█ █ █ █ ██████ ██ ██ ██ ██ ██ ", + "██████████████ ██ ██ ██ ██ ██ ", + " █████████████ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ", + " ████████ ████ ████████ █████ ", + " ", + " ███ ███ █ ██ ███ ███ ███ █████ ███ █████", + "█ █ █ █ ██ ██ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ ██ ██ █ █ █ ██ █ █ █ ██ █ █ █ ", + " █ █ █ █ █ ████ █ █ █ ███ █ █ █ █ █ █ ", + " █ ██ █ ██ █ █ █ ██ █ █ █ ██ █ █ █ █", + " █ █ █ ██ █ █ █ █ █ █ █ ██ █ █ █ █ ██ █ █ █", + "█████ ███ ███ ███ ███ ███ ██ ███ ███ ██ █████ ███ ", + " " +)) + +val testTimeAndDateSettingsMonthLithuanianScreen = makeDisplayFrame(arrayOf( + "█ █ █ █ █ █ █ ███ ", + "██ ██ █ █ █ █ █ █ ", + "█ █ █ █████ ██ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ ██ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ ", + "█ █ █████ █ █ ███ ███ ", + " ", + " █████ ", + "█████████████ ██ ██ ", + "█ ██ ██ ", + "██████████████ ██ ", + "█ █ █ █ █ █ ██ ██ ", + "██████████████ ██ ", + "█ █ █ █ █ █ ██ ███ ", + "██████████████ ██ ", + "█ █ █ █ █ █ ██ ██ ", + "██████████████ ██ ", + "█ █ █ █ ██████ ██ ", + "██████████████ ██ ", + " █████████████ ██ ", + " ██ ██ ", + " █████ ", + " ", + " ███ ███ █ ██ ███ ███ ███ █████ ███ █████", + "█ █ █ █ ██ ██ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ ██ ██ █ █ █ ██ █ █ █ ██ █ █ █ ", + " █ █ █ █ █ ████ █ █ █ ███ █ █ █ █ █ █ ", + " █ ██ █ ██ █ █ █ ██ █ █ █ ██ █ █ █ █", + " █ █ █ ██ █ █ █ █ █ █ █ ██ █ █ █ █ ██ █ █ █", + "█████ ███ ███ ███ ███ ███ ██ ███ ███ ██ █████ ███ ", + " " +)) + +val testTimeAndDateSettingsDayLithuanianScreen = makeDisplayFrame(arrayOf( + "███ ███ █████ █ █ █ ", + "█ █ █ █ █ █ █ █ ", + "█ █ █ █ ██ █ █ █ ", + "█ █ █ ████ █ █ █ █████ ", + "█ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ ", + "███ ███ █████ █ █ █ █ ", + " ", + " ████ ", + "█████████████ ██ ██ ", + "█ ██ ██ ██ ", + "██████████████ ██ ██ ", + "█ █ █ █ █ █ ██ ██ ██ ", + "██████████████ ██ ██ ", + "█ █ █ █ █ █ ██ ████ ", + "██████████████ ██ ██ ", + "█ █ █ █ █ █ ██ ██ ██ ", + "██████████████ ██ ██ ", + "█ █ █ █ ██████ ██ ██ ", + "██████████████ ██ ██ ", + " █████████████ ██ ██ ", + " ██ ██ ", + " ████ ", + " ", + " ███ ███ █ ██ ███ ███ ███ █████ ███ █████", + "█ █ █ █ ██ ██ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ ██ ██ █ █ █ ██ █ █ █ ██ █ █ █ ", + " █ █ █ █ █ ████ █ █ █ ███ █ █ █ █ █ █ ", + " █ ██ █ ██ █ █ █ ██ █ █ █ ██ █ █ █ █", + " █ █ █ ██ █ █ █ █ █ █ █ ██ █ █ █ █ ██ █ █ █", + "█████ ███ ███ ███ ███ ███ ██ ███ ███ ██ █████ ███ ", + " " +)) + val testUSDateFormatScreen = makeDisplayFrame(arrayOf( "███ █ █████ █ █ █ █ ████ █████ ███ ████ █ █ █ █████ ", "█ █ █ █ █ █ █ ██ ██ █ █ █ █ █ █ ██ ██ █ █ █ ", @@ -9712,3 +10429,1906 @@ val testMyDataBolusDataGermanScreen = makeDisplayFrame(arrayOf( " ███ ███ ██ ███ █ █ █ █ █ ███████ ███ ███ ██ ███ ███ ", " " )) + +val testMyDataTbrDataSlovenianScreen = makeDisplayFrame(arrayOf( + "████ ███ ███ █ █████ █ █ ███ ███ █████ ████ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "████ █ █ █ █ █████ █ ██ █ █ █ █ ████ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ███ ███ █ █ █ █ █ ███ ███ █████ ████ ███ ", + " ", + " █ █ █ ███ ██ ███ █ █████ ███ ", + " ███ ██ ██ █ █ ██ █ █ █ ██ █ █ █ █ ", + " █████ █ █ █ ██ █ █ ██ █ █ █ █ ██ ", + "███████ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ███ █ █ ██ █ █ ██ █ █ █ █ ██ █ ", + " ███ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", + " ███ ███ ███ ███ ██ ███ ███ ███ ███ ", + " ", + " █ ███ ███ ███ █ ", + " ██ █ █ █ █ ██ █ █ ██ ", + "███████ █ ██ █ ██ ██ █ ██ █ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ ██ █ █ ", + " ██ █ █ █ █ ██ █ █ █ ", + " █ ███ ███ ███ ███ ", + " ", + " ███ █ █████ █████ █ ███████ ███ ███ ███ █████ ", + " █ █ █ ██ █ ██ █ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ ██ █ █ ███████ █ ██ █ █ █ ██ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ ███ █ █ █ █ ", + "█ █ █ █ ██ █ █ ███████ ██ █ █ █ ██ █ █ ", + " █ █ █ █ ██ █ █ █ █ █ ███ █ █ █ █ ██ █ █ █ █ ", + " ███ ███ █ ███ ███ ███████ ███ ███ ██ ███ ███ ", + " " +)) + +val testMyDataErrorDataSlovenianScreen = makeDisplayFrame(arrayOf( + "████ ███ ███ █ █████ █ █ ███ ███ █ █ █ ████ █ █ █ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ ", + "████ █ █ █ █ █████ █ ██ █ █ █ █ █ █ █████ ████ █████ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ███ ███ █ █ █ █ █ ███ ███ █ █ █ █ █ █ █ █ █ ███ ", + " ", + " █ █ █ ██ ███ █ █████ ███ ", + " ███ █ █ █ █ █ ██ █ █ █ █ ", + " █ █ █ █ █ █ ██ █ █ █ █ ██ ", + " █ █ █ █ █ █ ████ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ ██ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███████ █ █ ███ ███ ███ ███ ███ ", + " ", + "████ ████ █████ █ █ █ ███ ███ █████ ████ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "████ ████ ████ ██ █ █ █ █ ████ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █████ █ █ █████ ███ ███ █████ ████ ███ ", + " ", + " ███ █ █████ █████ █ ███████ ███ ███ ███ █████ ", + " █ █ █ ██ █ ██ █ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ ██ █ █ ███████ █ ██ █ █ █ ██ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ ███ █ █ █ █ ", + "█ █ █ █ ██ █ █ ███████ ██ █ █ █ ██ █ █ ", + " █ █ █ █ ██ █ █ █ █ █ ███ █ █ █ █ ██ █ █ █ █ ", + " ███ ███ █ ███ ███ ███████ ███ ███ ██ ███ ███ ", + " " +)) + +val testMyDataDailyTotalsSlovenianScreen = makeDisplayFrame(arrayOf( + "███ █ █ █████ █ █ █ █ █ ████ ███ ████ █ ████ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ██ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ ████ █ █ █ █ █ █████ ████ █ █ ████ █████ ████ █████ ", + "█ █ █ ██ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ █ █ █████ █ █ █ █ █ █ ███ █ █ █ █ ████ █ █ ", + " ", + " ███ █ █████ ███ ", + " █ █ ██ █ █ █ █ ", + " █ ██ █ █ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ ", + " ██ █ █ █ █ ██ █ ", + " █ █ █ █ █ █ █ █ ", + " ███ ███ ███ ███ ", + " ", + "██████ █████ ███ █ █ █ ", + "█ █ █ █ █ ██ █ █ ", + " █ █ █ █ █ █ ", + " █ █ █ █ █ █ ", + " █ █ █ █ █ █ ", + "█ █ █ █ █ ██ █ █ █ ", + "██████ ███ █████ ██ ███ ███ ", + " ", + " ███████ ███ ███ ███ █████ ", + " █ █ █ █ █ █ █ █ █ ", + " ███████ █ ██ █ █ █ ██ █ ", + " █ █ █ █ █ █ █ ███ █ █ █ █ ", + " ███████ ██ █ █ █ ██ █ █ ", + " █ █ ███ █ █ █ █ ██ █ █ █ █ ", + " ███████ ███ ███ ██ ███ ███ ", + " " +)) + +val testMyDataBolusDataSlovenianScreen = makeDisplayFrame(arrayOf( + "████ ███ ███ █ █████ █ █ ███ ███ ████ ███ █ █ █ ████ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "████ █ █ █ █ █████ █ ██ █ █ █ ████ █ █ █ █ █ ███ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ███ ███ █ █ █ █ █ ███ ███ ████ ███ █████ ███ ████ ███ ", + " ", + "███ █████ ███ █ █ ███ █ █████ ███ ", + "█ █ █ █ █ █ █ █ █ ██ █ █ █ █ ", + "█ █ █ █ █ █ █ ██ █ █ █ █ ██ ", + "█ ██████ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ █ █ ██ █ ", + "█ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ███ ██ █████ ███ ███ ███ ███ ███ ", + " ", + " █ ███ ███ █ █████ ", + " ██ █ █ █ █ ██ ██ █ ", + "███████ █ ██ █ ██ ██ █ █ █ ", + "████████ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █████ █ ", + " ██ █ █ █ █ ██ █ █ █ ", + " █ ███ ███ █ ███ ", + " ", + " ███ ███ █████ ███ █ ███████ ███ █ ███ █ ", + " █ █ █ █ █ █ ██ █ █ ██ █ █ █ █ ██ █ █ ██ ", + "█ █ █ █ █ ██ █ █ ███████ █ ██ █ █ ██ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ ██ █ █ ███████ ██ █ █ ██ █ █ ", + " █ █ █ █ █ ██ █ █ █ █ ███ █ █ █ ██ █ █ █ ", + " ███ █████ ███ █████ ███ ███████ ███ ███ ██ ███ ███ ", + " " +)) + +val testMyDataTbrDataLithuanianScreen = makeDisplayFrame(arrayOf( + "█████ ████ ████ ███ █ █ ███ █ █ █████ █ █ █ █ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ ██ ██ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ ", + " █ ████ ████ █ █ █ █ █ █ █ █ █ ████ █ █ █ █ █ ███ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ ███ ███ ███ █ █ █████ █ █ █ ████ ", + " ", + " █ █ █ ███ ██ ███ █ █████ ███ ", + " ███ ██ ██ █ █ ██ █ █ █ ██ █ █ █ █ ", + " █████ █ █ █ ██ █ █ ██ █ █ █ █ ██ ", + "███████ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ███ █ █ ██ █ █ ██ █ █ █ █ ██ █ ", + " ███ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", + " ███ ███ ███ ███ ██ ███ ███ ███ ███ ", + " ", + " █ ███ ███ ███ █ ", + " ██ █ █ █ █ ██ █ █ ██ ", + "███████ █ ██ █ ██ ██ █ ██ █ ", + "████████ █ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ ██ █ █ ", + " ██ █ █ █ █ ██ █ █ █ ", + " █ ███ ███ ███ ███ ", + " ", + " ███ ███ ███ ███ ██ ███████ ███ ███ ███ █████ ", + " █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ ██ ██ █ ██ █ ███████ █ ██ █ █ █ ██ █ ", + "█ ██ █ █ █ █ █ █ █ █ ████ █ █ █ █ █ █ █ ███ █ █ █ █ ", + "█ █ █ ██ █ ██ ██ █ █ █ ███████ ██ █ █ █ ██ █ █ ", + " █ █ █ █ █ ██ █ █ █ █ █ █ ███ █ █ █ █ ██ █ █ █ █ ", + " ███ █████ ███ ███ ███ ███████ ███ ███ ██ ███ ███ ", + " " +)) + +val testMyDataErrorDataLithuanianScreen = makeDisplayFrame(arrayOf( + "█ █ █ █ ███ ███ ███ ████ ███ █ █ ███ █ █ █████ █ █ █ █ ████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ██ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ ", + "██ █ █████ █ █ █ █ █ ███ █ █ █ █ █ █ █ █ █ ████ █ █ █ █ █ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █████ █ █ ███ ███ ███ ████ ███ ███ ███ █ █ █████ █ █ █ ████ ", + " ", + " █ █ █ ██ ███ █ █████ ███ ", + " ███ █ █ █ █ █ ██ █ █ █ █ ", + " █ █ █ █ █ █ ██ █ █ █ █ ██ ", + " █ █ █ █ █ █ ████ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ ██ █ █ █ █ ██ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███████ █ █ ███ ███ ███ ███ ███ ", + " ", + "█████ ████ ████ █ █████ █ █ █ █ █ █ █ █████ █ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ ████ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ████ █████ █ █ █████ █ █ ██ █ █████ ███ ", + " █ █ █ █ █ █ █ █ ███ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ █ █ ████ █ █ ███ █ █ █ █ █ ████ ", + " ", + " ███ ███ ███ ███ ██ ███████ ███ ███ ███ █████ ", + " █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ ██ ██ █ ██ █ ███████ █ ██ █ █ █ ██ █ ", + "█ ██ █ █ █ █ █ █ █ █ ████ █ █ █ █ █ █ █ ███ █ █ █ █ ", + "█ █ █ ██ █ ██ ██ █ █ █ ███████ ██ █ █ █ ██ █ █ ", + " █ █ █ █ █ ██ █ █ █ █ █ █ ███ █ █ █ █ ██ █ █ █ █ ", + " ███ █████ ███ ███ ███ ███████ ███ ███ ██ ███ ███ ", + " " +)) + +val testMyDataDailyTotalsLithuanianScreen = makeDisplayFrame(arrayOf( + "████ █████ █ █ ███ ████ ███ ███ █████ █ █ ███ ████ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ ██ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + "████ ████ █ █ █ █ █ ████ █ █ █ ████ █ █ █ █ █ ███ ██ ", + "█ █ █ █ ██ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ ██ ", + "████ █████ █ █ ███ █ █ ██ ███ ███ █████ █ █ ███ ████ █ █ ██ ", + " ", + " ███ █ █████ ███ ", + " █ █ ██ █ █ █ █ ", + " █ ██ █ █ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ ", + " ██ █ █ █ █ ██ █ ", + " █ █ █ █ █ █ █ █ ", + " ███ ███ ███ ███ ", + " ", + "██████ █████ █████ ██ █ █ ", + "█ █ █ █ █ █ █ ", + " █ █ █ █ █ █ ", + " █ █ █ ████ █ █ ", + " █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ █ █ ", + "██████ ███ ███ ██ ███ ███ ", + " ", + " ███████ ███ ███ ███ █████ ", + " █ █ █ █ █ █ █ █ █ ", + " ███████ █ ██ █ █ █ ██ █ ", + " █ █ █ █ █ █ █ ███ █ █ █ █ ", + " ███████ ██ █ █ █ ██ █ █ ", + " █ █ ███ █ █ █ █ ██ █ █ █ █ ", + " ███████ ███ ███ ██ ███ ███ ", + " " +)) + +val testMyDataBolusDataLithuanianScreen = makeDisplayFrame(arrayOf( + "████ ███ █ ███ █ █ ████ ███ ███ █ █ ███ █ █ █████ █ █ █ █ ████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ██ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ ", + "████ █ █ █ █ █ █ ███ █ █ █ █ █ █ █ █ █ █ █ ████ █ █ █ █ █ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "████ ███ █████ ███ ███ ████ ███ ███ ███ ███ █ █ █████ █ █ █ ████ ", + " ", + "███ █████ ███ █ █ ███ █ █████ ███ ", + "█ █ █ █ █ █ █ █ █ ██ █ █ █ █ ", + "█ █ █ █ █ █ █ ██ █ █ █ █ ██ ", + "█ ██████ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ ██ █ █ █ █ ██ █ ", + "█ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ███ ██ █████ ███ ███ ███ ███ ███ ", + " ", + " █ ███ ███ █ █████ ", + " ██ █ █ █ █ ██ ██ █ ", + "███████ █ ██ █ ██ ██ █ █ █ ", + "████████ █ █ █ █ █ █ █ █ █ ", + "███████ ██ █ ██ █ ██ █████ █ ", + " ██ █ █ █ █ ██ █ █ █ ", + " █ ███ ███ █ ███ ", + " ", + " ███ ███ █████ ███ █ ███████ ███ █ ███ █ ", + " █ █ █ █ █ █ ██ █ █ ██ █ █ █ █ ██ █ █ ██ ", + "█ █ █ █ █ ██ █ █ ███████ █ ██ █ █ ██ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ ██ █ █ ███████ ██ █ █ ██ █ █ ", + " █ █ █ █ █ ██ █ █ █ █ ███ █ █ █ ██ █ █ █ ", + " ███ █████ ███ █████ ███ ███████ ███ ███ ██ ███ ███ ", + " " +)) + +object AlertSnoozeAndConfirmScreens { + + val testAlertScreenSnoozeTextEnglishScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ████ ███ █ █ █ ███ █████ █ █ █████ ███ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", + " █ ████ ████ █ █████ █ █ █ █ ████ █ █ ████ █ █ ", + " █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ ███ █ █ █ █ ███ █████ █████ █████ █████ ███ ", + " ", + " ██ ██ ██ ███ ████████ ", + " ████ ██ ██ ██ ██████████ ", + " █ █ ██ ██ ██ ████████████ ", + " ██ ██ ██ ██ ██ ██████████████ ", + " █ █ ██ ██ ██ ██ ████████████████ ", + " ██ ██ ██ ██ ██ ██ ██ █ █ █ █ ██ ", + " █ ██ █ ██ ██ ██ ██████ █ ███ ██ █ █ █ █ ", + " ██ ██ ██ ██ ██ ██ ███ ██ █ ██ ██ █ █ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ██ ██ ██ █ █ ███ ", + " ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ██ █ ███ ", + " █ █ ██ ██ ██ ██ ██ ████████████████ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ██████████████ ", + " █ █ ██████████ ██ ██ ████████████ ", + " ████████████████ ███ ███ ██ ██ ██████████ ", + " ███████████████ █ █ ████ ████████ ", + " ", + " ", + " █ █████ ███ ████ █ █ ███ ███ █████ █████ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ ██ █ █ █ █ █ █ █ ", + "███ █ █ █ ███ █ █ █ █ █ █ █ █ ████ ", + " █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ███ ████ █ █ ███ ███ █████ █████ " + ) + ) + + val testAlertScreenConfirmTextEnglishScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ████ ███ █ █ █ ███ █████ █ █ █████ ███ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", + " █ ████ ████ █ █████ █ █ █ █ ████ █ █ ████ █ █ ", + " █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ ███ █ █ █ █ ███ █████ █████ █████ █████ ███ ", + " ", + " ██ ██ ██ ███ ████████ ", + " ████ ██ ██ ██ ██████████ ", + " █ █ ██ ██ ██ ████████████ ", + " ██ ██ ██ ██ ██ ██████████████ ", + " █ █ ██ ██ ██ ██ ████████████████ ", + " ██ ██ ██ ██ ██ ██ ██ █ █ █ █ ██ ", + " █ ██ █ ██ ██ ██ ██████ █ ███ ██ █ █ █ █ ", + " ██ ██ ██ ██ ██ ██ ███ ██ █ ██ ██ █ █ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ██ ██ ██ █ █ ███ ", + " ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ██ █ ███ ", + " █ █ ██ ██ ██ ██ ██ ████████████████ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ██████████████ ", + " █ █ ██████████ ██ ██ ████████████ ", + " ████████████████ ███ ███ ██ ██ ██████████ ", + " ███████████████ █ █ ████ ████████ ", + " ", + " ", + " █ █████ ███ ███ ███ █ █ █████ ███ ████ █ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ██ ", + "█ ██ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", + "███ █ █ █ █ █ █ █ █ █ ████ █ ████ █ █ █ ", + " █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ███ ███ ███ █ █ █ ███ █ █ █ █ " + ) + ) + + val testAlertScreenSnoozeTextSpanishScreen = makeDisplayFrame( + arrayOf( + "███ ████ █████ ███ █ █ █ ███ █████ █ █ ███ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ █ █████ █ █ █ █ ████ █ █████ █ █ █████ ", + "█ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ ███ █ █ █ █ ███ █████ █████ █ █ ███ █ █ ", + " ", + " ██ ██ ██ ███ ████████ ", + " ████ ██ ██ ██ ██████████ ", + " █ █ ██ ██ ██ ████████████ ", + " ██ ██ ██ ██ ██ ██████████████ ", + " █ █ ██ ██ ██ ██ ████████████████ ", + " ██ ██ ██ ██ ██ ██ ██ █ █ █ █ ██ ", + " █ ██ █ ██ ██ ██ ██████ █ ███ ██ █ █ █ █ ", + " ██ ██ ██ ██ ██ ██ ███ ██ █ ██ ██ █ █ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ██ ██ ██ █ █ ███ ", + " ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ██ █ ███ ", + " █ █ ██ ██ ██ ██ ██ ████████████████ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ██████████████ ", + " █ █ ██████████ ██ ██ ████████████ ", + " ████████████████ ███ ███ ██ ██ ██████████ ", + " ███████████████ █ █ ████ ████████ ", + " ", + " ", + " █ ████ █████ ████ █████ █████ ███ ████ ████ █████ █ █ █ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ ████ ████ ████ █ █ ████ ███ ████ ██ █ █████ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ ", + " █ █ █████ █ █████ █ ███ █ █ ████ █████ █ █ █ █ █████ " + ) + ) + + val testAlertScreenConfirmTextSpanishScreen = makeDisplayFrame( + arrayOf( + "███ ████ █████ ███ █ █ █ ███ █████ █ █ ███ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ █ █████ █ █ █ █ ████ █ █████ █ █ █████ ", + "█ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ ███ █ █ █ █ ███ █████ █████ █ █ ███ █ █ ", + " ", + " ██ ██ ██ ███ ████████ ", + " ████ ██ ██ ██ ██████████ ", + " █ █ ██ ██ ██ ████████████ ", + " ██ ██ ██ ██ ██ ██████████████ ", + " █ █ ██ ██ ██ ██ ████████████████ ", + " ██ ██ ██ ██ ██ ██ ██ █ █ █ █ ██ ", + " █ ██ █ ██ ██ ██ ██████ █ ███ ██ █ █ █ █ ", + " ██ ██ ██ ██ ██ ██ ███ ██ █ ██ ██ █ █ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ██ ██ ██ █ █ ███ ", + " ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ██ █ ███ ", + " █ █ ██ ██ ██ ██ ██ ████████████████ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ██████████████ ", + " █ █ ██████████ ██ ██ ████████████ ", + " ████████████████ ███ ███ ██ ██ ██████████ ", + " ███████████████ █ █ ████ ████████ ", + " ", + " ", + " █ ███ ███ █ █ █████ ███ ████ █ █ █ ████ ", + " ██ █ █ █ █ █ █ █ █ █ █ ██ ██ █ █ █ █ ", + "█ ██ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ █ █ █ █ █ █ ████ █ ████ █ █ █ █████ ████ ", + " █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ███ ███ █ █ █ ███ █ █ █ █ █ █ █ █ " + ) + ) + + val testAlertScreenSnoozeTextFrenchScreen = makeDisplayFrame( + arrayOf( + "███ ████ █████ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ ██ █ ██ █ █ █ █ █████ ", + "█ █ ████ █ █████ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ ██ █ ██ █ █ █ ████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ █ █ █ █ █ █ ███ █████ █████ ", + " ", + " ██ ██ ██ ███ ████████ ", + " ████ ██ ██ ██ ██████████ ", + " █ █ ██ ██ ██ ████████████ ", + " ██ ██ ██ ██ ██ ██████████████ ", + " █ █ ██ ██ ██ ██ ████████████████ ", + " ██ ██ ██ ██ ██ ██ ██ █ █ █ █ ██ ", + " █ ██ █ ██ ██ ██ ██████ █ ███ ██ █ █ █ █ ", + " ██ ██ ██ ██ ██ ██ ███ ██ █ ██ ██ █ █ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ██ ██ ██ █ █ ███ ", + " ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ██ █ ███ ", + " █ █ ██ ██ ██ ██ ██ ████████████████ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ██████████████ ", + " █ █ ██████████ ██ ██ ████████████ ", + " ████████████████ ███ ███ ██ ██ ██████████ ", + " ███████████████ █ █ ████ ████████ ", + " ", + " ", + " █ ████ █ ████ ████ █████ █ █████ █ ████ ███ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █████ ████ ████ ████ █ █████ █ █████ ████ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █████ █████ █ █ █ █ █ ███ " + ) + ) + + val testAlertScreenConfirmTextFrenchScreen = makeDisplayFrame( + arrayOf( + "███ ████ █████ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ ██ █ ██ █ █ █ █ █████ ", + "█ █ ████ █ █████ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ ██ █ ██ █ █ █ ████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ █ █ █ █ █ █ ███ █████ █████ ", + " ", + " ██ ██ ██ ███ ████████ ", + " ████ ██ ██ ██ ██████████ ", + " █ █ ██ ██ ██ ████████████ ", + " ██ ██ ██ ██ ██ ██████████████ ", + " █ █ ██ ██ ██ ██ ████████████████ ", + " ██ ██ ██ ██ ██ ██ ██ █ █ █ █ ██ ", + " █ ██ █ ██ ██ ██ ██████ █ ███ ██ █ █ █ █ ", + " ██ ██ ██ ██ ██ ██ ███ ██ █ ██ ██ █ █ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ██ ██ ██ █ █ ███ ", + " ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ██ █ ███ ", + " █ █ ██ ██ ██ ██ ██ ████████████████ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ██████████████ ", + " █ █ ██████████ ██ ██ ████████████ ", + " ████████████████ ███ ███ ██ ██ ██████████ ", + " ███████████████ █ █ ████ ████████ ", + " ", + " ", + " █ ████ ███ █ █ ████ ███ ███ █ █ █████ ███ ████ █ █ █████ ████ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ██ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ █ █ █ ████ █ █ █ █ █ █ ████ █ ████ █ █ █ ████ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ███ ███ █ █ ███ ███ █ █ █ ███ █ █ █ █ █████ █ █ " + ) + ) + + val testAlertScreenSnoozeTextItalianScreen = makeDisplayFrame( + arrayOf( + "████ ████ █████ █ █ █ █ █ █ █ █ █ █ █████ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ ██ █ ██ █ █ █ █ █ █ █ █ █ █ ", + "████ ████ █ █████ █ █ █ █ █ █ █ █ █ █ █████ █ █ █ ", + "█ █ █ █ █ █ █ ██ █ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ████ █ █ █ █ █ █ █ ███ █████ █████ █ █ █ ███ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ████ ███ ████ █████ █████ ███ █ █ █ █ ████ █ █ █████ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ██ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ ████ ████ █ █ █████ █ █ █████ ████ █ █ █ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ ███ █ █████ █ ███ █ █ █████ █████ █ █ █ █ █ █ █████ " + ) + ) + + val testAlertScreenConfirmTextItalianScreen = makeDisplayFrame( + arrayOf( + "████ ████ █████ █ █ █ █ █ █ █ █ █ █ █████ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ ██ █ ██ █ █ █ █ █ █ █ █ █ █ ", + "████ ████ █ █████ █ █ █ █ █ █ █ █ █ █ █████ █ █ █ ", + "█ █ █ █ █ █ █ ██ █ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ████ █ █ █ █ █ █ █ ███ █████ █████ █ █ █ ███ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ████ █████ ████ ███ ███ █ █ █████ █████ ████ █ █ █ ████ █████ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ██ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ ████ ████ █ █ █ █ █ █ ████ ████ ████ █ █ █ █████ ████ ████ ", + " █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █████ █ █ ███ ███ █ █ █ █████ █ █ █ █ █ █ █ █ █████ " + ) + ) + + val testAlertScreenSnoozeTextRussianScreen = makeDisplayFrame( + arrayOf( + "████ █████ ███ ███ █████ █ █ █████ █ █ █████ █ █ █ ", + "█ █ █ █ █ █ █ █ ██ ██ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "████ ████ █ █ █ █ █ █ ████ █████ ████ █████ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "████ ████ ███ ███ █ █ █ █████ █ █ █████ █ █ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ████ █ █ █ █ ████ ███ ████ █ █ █ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ ██ █ ██ █ █ ██ ████ ███ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", + " ████ ██ █ █ █ ██ █ ██ ███ ████ █ █ █ " + ) + ) + + val testAlertScreenConfirmTextRussianScreen = makeDisplayFrame( + arrayOf( + "████ █████ ███ ███ █████ █ █ █████ █ █ █████ █ █ █ ", + "█ █ █ █ █ █ █ █ ██ ██ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "████ ████ █ █ █ █ █ █ ████ █████ ████ █████ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "████ ████ ███ ███ █ █ █ █████ █ █ █████ █ █ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █████ ███ ██ █████ ████ █████ ████ ██ █ █ █████ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ █ █ █ █ █ █ █ ████ ████ ████ █ █ █ █ █ █ ███ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █████ █ █ █ █ █ █████ ██ █ █ █ █ ", + " █ █ ███ █ █ █ ████ █████ █ █ █ █ █ █ ███ " + ) + ) + + val testAlertScreenSnoozeTextTurkishScreen = makeDisplayFrame( + arrayOf( + " ███ ████ █ █ █ ████ █████ █ █ █████ ███ █ █ ███ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ ███ █ █ █ █ █ █ █ █ █ ███ █ █ █ ███ ", + "█ ███ ████ █████ █ ████ █ █████ █ ████ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ████ ████ █ █ ███ █ █ █ █ █████ █████ ███ ███ █████ ███ ███ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █████ ████ █████ █████ █ █████ ", + " ██ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ ", + "███ ████ ████ █ ████ █ ████ ", + " █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ ", + " █████ █ █ █ █████ █████ █████ " + ) + ) + + val testAlertScreenConfirmTextTurkishScreen = makeDisplayFrame( + arrayOf( + " ███ ████ █ █ █ ████ █████ █ █ █████ ███ █ █ ███ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ ███ █ █ █ █ █ █ █ █ █ ███ █ █ █ ███ ", + "█ ███ ████ █████ █ ████ █ █████ █ ████ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ████ ████ █ █ ███ █ █ █ █ █████ █████ ███ ███ █████ ███ ███ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ███ █ █ █ █ █ █ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ ██ █ █ █ █ █ █ █ █ ", + "███ █ █ █ █ █ █████ █ █ █ █████ ", + " █ █ █ █ ██ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ ", + " ███ █ █ █ █ █ █████ █ █ " + ) + ) + + val testAlertScreenSnoozeTextPolishScreen = makeDisplayFrame( + arrayOf( + " █ █ █ █ █ █ ███ █ █ █ █ █ ███ █████ ███ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ██ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", + "█████ █ █ █ █ █ █ █ █ █ █ █ █████ █ █ █ █ █ █ █ █ ████ ", + "█ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ ███ █████ ███ █ █ █ █ █ █ ███ █ ███ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █ ████ █ █ █ █ █ █ ███ ███ ████ █████ █ █ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ████ ", + "███ █████ ████ █ █ █ █ █ █ █ █ █ ███ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ ████ █ █ █ █ ███ ███ ████ █████ █ ████ " + ) + ) + + val testAlertScreenConfirmTextPolishScreen = makeDisplayFrame( + arrayOf( + " █ █ █ █ █ █ ███ █ █ █ █ █ ███ █████ ███ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ██ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ ", + "█████ █ █ █ █ █ █ █ █ █ █ █ █████ █ █ █ █ █ █ █ █ ████ ", + "█ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ ███ █████ ███ █ █ █ █ █ █ ███ █ ███ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █ ████ █ █ ████ ███ █████ █ █ ███ █████ ████ ███ █████ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ █████ ████ █ █ ████ █ █ █ █ █ █ █ ████ ████ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ", + " █ █ ████ █ █ ███ █ █ █ ███ █████ █ █ ███ █████ ██ " + ) + ) + + val testAlertScreenSnoozeTextCzechScreen = makeDisplayFrame( + arrayOf( + "███ ████ ███ █████ ████ █ █ █ █ █████ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ ████ █ ██ █ █ █ ", + "█ █ ████ █ █ █ ████ █ █ █ ████ █ █ █ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ ███ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ ███ █████ █ █ ███ ████ █████ █ █ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ███ ███ █ ███ █ █ ███ █████ ", + " ██ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █████ █ █ ", + "███ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ ", + " ███ ███ █████ ███ █████ ███ █ " + ) + ) + + val testAlertScreenConfirmTextCzechScreen = makeDisplayFrame( + arrayOf( + "███ ████ ███ █████ ████ █ █ █ █ █████ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ ████ █ ██ █ █ █ ", + "█ █ ████ █ █ █ ████ █ █ █ ████ █ █ █ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ ███ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ ███ █████ █ █ ███ ████ █████ █ █ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ████ ███ █████ █ █ ████ ███ ███ █████ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ █ █ █ █ ████ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ███ █ █ █ █ ███ ███ █ " + ) + ) + + val testAlertScreenSnoozeTextHungarianScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ████ █ █ █████ ███ ████ █████ █ █ █ █ ███ █████ ", + " █ █ █ █ █ ██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ████ █ █ █ ████ █ ███ ███ █ █████ ██ █████ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ █ █████ ████ ████ █████ █ █ █ █ █ █ ███ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █ █ █ █ █ █ █████ █ ████ ", + " ██ █ █ █ ██ ██ █ █ █ █ ", + "█ ██ ██ █ █████ █ █ █ ███ █ █ █ ", + "███ █ █ █ █ █ █ █ █ █ █ █ ███ ", + " █ █ ██ ████ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █████ █ ", + " █ █ █████ █ █ ███ █ █ █ ████ " + ) + ) + + val testAlertScreenConfirmTextHungarianScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ████ █ █ █████ ███ ████ █████ █ █ █ █ ███ █████ ", + " █ █ █ █ █ ██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ████ █ █ █ ████ █ ███ ███ █ █████ ██ █████ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ █ █████ ████ ████ █████ █ █ █ █ █ █ ███ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ███ █ █ █ █ █ █ █ ███ █ █ █ ████ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ ███ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ █ █ █ █ █ █ █ █████ █████ █ ███ █ █ █ █ ███ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █████ █ █ █ █ █ █ █ █████ █ ", + " ██ ███ █ █ █ █ █ █ █ ████ █ █ █ ████ " + ) + ) + + val testAlertScreenSnoozeTextSlovakScreen = makeDisplayFrame( + arrayOf( + "███ ████ ███ █████ ████ █ █ █ █ █████ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ ████ █ ██ █ █ ", + "█ █ ████ █ █ █ ████ █ █ █ ████ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ ███ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █████ ", + "███ ████ ███ █████ █ █ ███ ████ █████ █ █ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ████ █████ █ █ █ ███ █ █ ", + " ██ █ █ █ ██ ██ █ █ ", + "█ ██ █ █ █ █ █ █ █ █████ ", + "███ ███ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ ", + " ████ █ █████ █ █ ███ █ " + ) + ) + + val testAlertScreenConfirmTextSlovakScreen = makeDisplayFrame( + arrayOf( + "███ ████ ███ █████ ████ █ █ █ █ █████ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ ████ █ ██ █ █ ", + "█ █ ████ █ █ █ ████ █ █ █ ████ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ ███ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █████ ", + "███ ████ ███ █████ █ █ ███ ████ █████ █ █ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ████ ███ █████ █ █ ████ ███ ███ █ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █████ ", + "███ ████ █ █ █ █ █ ████ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ███ █ █ █ █ ███ ███ █ " + ) + ) + + val testAlertScreenSnoozeTextRomanianScreen = makeDisplayFrame( + arrayOf( + "████ ████ █████ █ █ █ █ █ █ █ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", + "████ ████ █ █████ █ █ █ █ █ █ █████ █ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ █ █ █ █ ███ █████ █ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ███ ████ ████ ███ ████ █████ ████ ███ █ █ █████ ████ ███ █████ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + "███ █ █ ████ ████ █ ████ ████ ███ █ █ █ █ █ ████ ████ █ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ███ █ █ █ ███ █ █ █████ ████ ███ █ █ █████ █ █ ███ █████ " + ) + ) + + val testAlertScreenConfirmTextRomanianScreen = makeDisplayFrame( + arrayOf( + "████ ████ █████ █ █ █ █ █ █ █ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", + "████ ████ █ █████ █ █ █ █ █ █ █████ █ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ █ █ █ █ ███ █████ █ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ███ ███ █ █ █████ ███ ████ █ █ █ ████ █████ ", + " ██ █ █ █ █ █ █ █ █ █ █ ██ ██ █ █ █ █ █ ", + "█ ██ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ █ █ █ █ █ █ ████ █ ████ █ █ █ █████ ████ ████ ", + " █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ███ ███ █ █ █ ███ █ █ █ █ █ █ █ █ █████ " + ) + ) + + val testAlertScreenSnoozeTextCroatianScreen = makeDisplayFrame( + arrayOf( + "████ ████ ███ ███ █████ █ █ █ █████ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ ", + "████ ████ █ █ █ █ █ ██ █████ █ █████ █ █ █ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ████ ███ ███ █ █ █ █ █ █████ █ █ █ █ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █████ █ ███ ███ ███ ███ ███ █ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ █ █████ █ █ █ █ █ ███ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █████ █ █ ███ ███ ████ ███ ███ ███ " + ) + ) + + val testAlertScreenConfirmTextCroatianScreen = makeDisplayFrame( + arrayOf( + "████ ████ ███ ███ █████ █ █ █ █████ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ ", + "████ ████ █ █ █ █ █ ██ █████ █ █████ █ █ █ █████ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ████ ███ ███ █ █ █ █ █ █████ █ █ █ █ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █████ █ ████ ███ █████ █ █ ████ ███ █ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ █ █████ ████ █ █ █ █ █ ████ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █████ █ █ █ ███ █ █ █ █ ███ ███ " + ) + ) + + val testAlertScreenSnoozeTextDutchScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ███ ███ █████ █ █ █ █ █ █ █ █ █████ █████ ████ ███ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ ██ █ ██ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ ███ ████ █████ █ █ █ █ █ █ █ █ █ ████ ████ ████ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ ██ █ ██ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ███ ████ █████ █ █ █ █ █ █ ███ █████ █████ █████ █ █ ███ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █ █ ███ █████ ████ █████ █████ █ █ █████ █ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ ██ █ ", + "███ █ █ █ █ ███ █ ████ █ █ ████ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ", + " ███ ███ █ ████ █ █████ █████ █████ █████ █ █ " + ) + ) + + val testAlertScreenConfirmTextDutchScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ███ ███ █████ █ █ █ █ █ █ █ █ █████ █████ ████ ███ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ ██ █ ██ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ ███ ████ █████ █ █ █ █ █ █ █ █ █ ████ ████ ████ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ ██ █ ██ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ███ ████ █████ █ █ █ █ █ █ ███ █████ █████ █████ █ █ ███ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ████ █████ █ █ █████ ████ █████ ███ ███ █████ █ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", + "███ ████ ████ █ █ ████ ███ █ █ █ ███ ████ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ████ █████ █ █████ ████ █ ███ ████ █████ █ █ " + ) + ) + + val testAlertScreenSnoozeTextGreekScreen = makeDisplayFrame( + arrayOf( + " █ █ █ █ █ ████ ███ █████ █ █ █████ ████ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ██ ███ ████ █ █ █ █████ █ █ ████ ████ ", + "█████ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ██ █ ██ ", + "█ █ █ █ █ █ ██ ██ █████ █ █ █ █ ██ ████ ██ █ ██ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █ █ █ █ █████ █████ ███ █ █ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ ", + "███ █████ █ █ █ █████ █ █ █ █ █ █ █████ ", + " █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █████ █ ███ █ █ █ █ " + ) + ) + + val testAlertScreenConfirmTextGreekScreen = makeDisplayFrame( + arrayOf( + " █ █ █ █ █ ████ ███ █████ █ █ █████ ████ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ██ ███ ████ █ █ █ █████ █ █ ████ ████ ", + "█████ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ██ █ ██ ", + "█ █ █ █ █ █ ██ ██ █████ █ █ █ █ ██ ████ ██ █ ██ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █████ █████ ███ ████ █████ ████ █ ███ ███ █████ █ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ █ █ ████ ████ ████ █ █ █ █ █ █ █████ ", + " █ █ █ █ █ █ █ █ █ █ █████ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █████ █ █ ███ ████ █████ ████ █ █ ███ ██ ██ █████ █ █ " + ) + ) + + val testAlertScreenSnoozeTextFinnishScreen = makeDisplayFrame( + arrayOf( + "█████ ████ █ ████ █████ ████ █ █ █ █ █████ █████ █████ █████ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █████ ████ ████ ████ █ █ █ █ █ ████ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ █████ █ █ ███ ███ █ █████ █ █ ███ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ███ █ █ █ ███ ███ █████ █ █ █ █ █ █ █ █ █ ", + " ██ █ █ ██ ██ █ █ █ █ █ █ ██ ██ █ █ ███ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ █ █ █ █ █ █ █ █ █ █████ █ █ █ █ █ █ █ █████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ", + " ███ █████ █ █ ███ ███ █ █ █ █ █ █ ███ █ █ ██ " + ) + ) + + val testAlertScreenConfirmTextFinnishScreen = makeDisplayFrame( + arrayOf( + "█████ ████ █ ████ █████ ████ █ █ █ █ █████ █████ █████ █████ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █████ ████ ████ ████ █ █ █ █ █ ████ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ █████ █ █ ███ ███ █ █████ █ █ ███ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █ █ █ █ █ █ █ ███ ████ █████ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ █ █ █████ █████ █ █ █ ███ █ █████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ ███ ████ █ █ █ " + ) + ) + + val testAlertScreenSnoozeTextNorwegianScreen = makeDisplayFrame( + arrayOf( + "█ █ ████ ███ █ █ █ ████ ████ █ █ █████ █████ ", + "██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ ████ █ █ █████ █ █ ████ ████ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ ███ █ █ █ ████ █ █ ███ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █████ ███ ████ █ ████ █ █ █ █ █ ████ █████ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ ██ ██ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ █ ████ █ █ ███ █ █ █ █ █ █ ████ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █████ █ █ █ █ █ █ █ █ █ ", + " █ ███ █ █ █ █ ████ █████ ███ █ █ █ █ █████ " + ) + ) + + val testAlertScreenConfirmTextNorwegianScreen = makeDisplayFrame( + arrayOf( + "█ █ ████ ███ █ █ █ ████ ████ █ █ █████ █████ ", + "██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ ████ █ █ █████ █ █ ████ ████ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ ███ █ █ █ ████ █ █ ███ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █████ ███ ████ █ ████ █████ █ █ ████ █████ █████ █████ █████ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ █ ████ █ █ ████ ████ ██ ████ ████ ████ █ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █████ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ███ █ █ █ █ ████ █████ █ █ █ █ █████ █ █ █████ " + ) + ) + + val testAlertScreenSnoozeTextPortugueseScreen = makeDisplayFrame( + arrayOf( + "███ ████ █████ ███ █ █ █ ███ █████ █ █ ███ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ █ █████ █ █ █ █ ████ █ █████ █ █ █████ ", + "█ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ ███ █ █ █ █ ███ █████ █████ █ █ ███ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ████ █ ████ █ ████ ███ █ █████ █ █ ███ ███ █ ████ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ ", + "███ ████ █████ ████ █████ ███ █ █ ████ █ █ █ █ █ █████ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ ████ ███ █████ █████ █ █ ███ ███ █ █ █ █ " + ) + ) + + val testAlertScreenConfirmTextPortugueseScreen = makeDisplayFrame( + arrayOf( + "███ ████ █████ ███ █ █ █ ███ █████ █ █ ███ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ █ █████ █ █ █ █ ████ █ █████ █ █ █████ ", + "█ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ ███ █ █ █ █ ███ █████ █████ █ █ ███ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ████ █ ████ █ ███ ███ █ █ █████ ███ ████ █ █ █ ████ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ██ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █████ ████ █████ █ █ █ █ █ █ ████ █ ████ █ █ █ █████ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ ███ ███ █ █ █ ███ █ █ █ █ █ █ █ █ " + ) + ) + + val testAlertScreenSnoozeTextSwedishScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ███ █ █ █ ████ ████ █ █ █████ █████ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", + " █ ████ █ █ █████ █ █ ████ ████ █ █ █ ████ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ███ █ █ █ ████ █ █ ███ █ █████ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ████ █ █ ███ ███ █████ █████ ", + " ██ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ ██ █ █ █ █ █ █ █ ", + "███ ███ █ █ █ █ █ █ █ █ ████ ", + " █ █ █ ██ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ ", + " ████ █ █ ███ ███ █████ █████ " + ) + ) + + val testAlertScreenConfirmTextSwedishScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ███ █ █ █ ████ ████ █ █ █████ █████ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", + " █ ████ █ █ █████ █ █ ████ ████ █ █ █ ████ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ███ █ █ █ ████ █ █ ███ █ █████ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ████ █████ █ █ ████ █ █ █████ █████ █ ", + " ██ █ █ █ █ █ █ █ ███ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ ████ ██ ████ █ █ ████ █ █████ ", + " █ █ █ █ █ █ █ █ █████ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ████ █████ █ █ █ █ █ █ █ █ █ █ " + ) + ) + + val testAlertScreenSnoozeTextDanishScreen = makeDisplayFrame( + arrayOf( + "█ █ ████ ████ █ █ █ █ █ █ █ █ █ █████ ████ █████ █████ ", + "██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ █ ██ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ ████ ████ █████ █ █ █ █ █ █ █ █ █ █ ████ ████ ████ █ ", + "█ █ █ █ █ █ █ █ █ ██ █ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ █ █ █ █ █ █ █ ███ █████ █████ █████ █ █ █████ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █████ ███ ████ █ █████ █ █ ███ ████ ████ █████ █████ █████ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ █ ████ █████ █ █ █ █ █ ███ ████ █ █ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ███ █ █ █ █ █ ███ ███ ████ █ ███ █ █ █████ " + ) + ) + + val testAlertScreenConfirmTextDanishScreen = makeDisplayFrame( + arrayOf( + "█ █ ████ ████ █ █ █ █ █ █ █ █ █ █████ ████ █████ █████ ", + "██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ ██ █ ██ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ ████ ████ █████ █ █ █ █ █ █ █ █ █ █ ████ ████ ████ █ ", + "█ █ █ █ █ █ █ █ █ ██ █ ██ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ ████ █ █ █ █ █ █ █ █ ███ █████ █████ █████ █ █ █████ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █████ ███ ████ ███ ███ ███ █ █ █████ █ █ ███ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ ", + "███ ████ █ █ ████ █ ███ █ █ █ █ ██ ████ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ███ █ █ ████ ███ ███ █ █ █████ █ █ ███ " + ) + ) + + val testAlertScreenSnoozeTextGermanScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ████ █ ████ ████ ████ █ █ ███ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ████ █████ ████ ████ ████ █ █ █ █████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ █ ████ ████ █ █ ███ ███ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █ █ █████ █ █ █████ ████ ███ █ █ █ █ █████ ████ █ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ ██ █ █ █ █ █ █ █ █ ██ █ ██ █ █ █ █ ██ █ ", + "███ █ █ █ ████ █ █ ████ ████ █ █ █ █ █ █ █ ████ ████ █ █ █ ", + " █ █ ██ █ █ █ █ █ █ █ █ ██ █ ██ █ █ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █████ ███ █████ █ █ ███ █ █ █ █ █████ █ █ █ █ " + ) + ) + + val testAlertScreenConfirmTextGermanScreen = makeDisplayFrame( + arrayOf( + "█████ ████ ████ █ ████ ████ ████ █ █ ███ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ████ █████ ████ ████ ████ █ █ █ █████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ █ ████ ████ █ █ ███ ███ █ █ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ████ █████ ████ █████ █ █ █████ ███ ███ █████ █ █ ", + " ██ █ █ █ █ █ ███ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ ██ █ ", + "███ ████ ████ ███ █ █ █ █ █ █ ███ ████ █ █ █ ", + " █ █ █ █ █ █ █████ █ █ █ █ █ █ ██ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " ████ █████ ████ █ █ █ █ ███ ████ █████ █ █ " + ) + ) + + val testAlertScreenSnoozeTextSlovenianScreen = makeDisplayFrame(arrayOf( + "████ ████ █████ █ █ █ ███ ███ █████ ████ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "████ ████ ████ ██ █ █ █ █ ████ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █████ █ █ █████ ███ ███ █████ ████ ███ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █ █ █████ ███ █ █ █ █ █ ███ █████ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ ████ █ █ ██ █ █ █ ", + "███ █ █ █ █ █ █████ █ █ █ █ ████ ", + " █ █ █ █ █ ███ █ █ █ ██ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ", + " ███ █ ███ ████ █ █ █ █ ██ █████ " + )) + + val testAlertScreenConfirmTextSlovenianScreen = makeDisplayFrame(arrayOf( + "████ ████ █████ █ █ █ ███ ███ █████ ████ ███ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "████ ████ ████ ██ █ █ █ █ ████ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ █ █ █████ █ █ █████ ███ ███ █████ ████ ███ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ████ ███ █████ ████ ███ ███ █████ █████ █ █ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ ████ █ █ █ ████ █ █ █ █ ████ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ███ █ █ █ ███ ███ █ █████ █ " + )) + + val testAlertScreenSnoozeTextLithuanianScreen = makeDisplayFrame(arrayOf( + "█████ ████ ████ █ █████ █ █ █ █ █ █ █ █████ █ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ ████ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ████ █████ █ █ █████ █ █ ██ █ █████ ███ ", + " █ █ █ █ █ █ █ █ ███ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ █ █ ████ █ █ ███ █ █ █ █ █ ████ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ █ █ █ █ █████ ███ █ ███ █ █ █████ ███ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ ██ █ █ █ █ █ █ █ █ █ █ █ █ ", + "███ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ ██ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ ███ █ ███ █████ ███ █ █ ███ " + )) + + val testAlertScreenConfirmTextLithuanianScreen = makeDisplayFrame(arrayOf( + "█████ ████ ████ █ █████ █ █ █ █ █ █ █ █████ █ ████ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ ████ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ ████ █████ █ █ █████ █ █ ██ █ █████ ███ ", + " █ █ █ █ █ █ █ █ ███ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ ████ █ █ █ █ █ ████ █ █ ███ █ █ █ █ █ ████ ", + " ", + " ██ ██ ██ ███ ", + " ████ ██ ██ ██ ", + " █ █ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ", + " █ ██ █ ██ ██ ██ ██████ ", + " ██ ██ ██ ██ ██ ██ ███ ██ ", + " █ ██ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ██ ██ ██ ██ ", + " █ █ ██ ██ ██ ██ ██ ", + " ██ ██ ██ ██ ████ ██ ██ ██ ", + " █ █ ██████████ ██ ██ ", + " ████████████████ ███ ███ ██ ██ ", + " ███████████████ █ █ ████ ", + " ", + " ", + " █ ████ █ █████ █ █ ███ ████ █████ ███ █ █ █████ ███ ", + " ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + "█ ██ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ ", + "███ ████ █████ █ █ █ █ ████ █ █ █ █ █ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ ", + " █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ", + " █ █ █ █ █ ███ █ █ █ ███ █ █ █ ███ " + )) +} \ No newline at end of file diff --git a/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/AAPSPumpStateStore.kt b/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/AAPSPumpStateStore.kt index 163b7518c3..f55ad8ba4c 100644 --- a/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/AAPSPumpStateStore.kt +++ b/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/AAPSPumpStateStore.kt @@ -5,6 +5,7 @@ import info.nightscout.comboctl.base.CurrentTbrState import info.nightscout.comboctl.base.InvariantPumpData import info.nightscout.comboctl.base.Nonce import info.nightscout.comboctl.base.PumpStateStore +import info.nightscout.comboctl.base.PumpStateStoreAccessException import info.nightscout.comboctl.base.Tbr import info.nightscout.comboctl.base.toBluetoothAddress import info.nightscout.comboctl.base.toCipher @@ -151,7 +152,7 @@ class AAPSPumpStateStore( timestamp = Instant.fromEpochSeconds(tbrTimestamp), percentage = tbrPercentage, durationInMinutes = tbrDuration, - type = Tbr.Type.fromStringId(tbrType)!! + type = Tbr.Type.fromStringId(tbrType) ?: throw PumpStateStoreAccessException(pumpAddress, "Invalid type \"$tbrType\"") )) else CurrentTbrState.NoTbrOngoing diff --git a/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/ComboV2Fragment.kt b/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/ComboV2Fragment.kt index cc5646e2a9..8439dd4295 100644 --- a/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/ComboV2Fragment.kt +++ b/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/ComboV2Fragment.kt @@ -94,8 +94,13 @@ class ComboV2Fragment : DaggerFragment() { binding.combov2DriverState.text = text binding.combov2RefreshButton.isEnabled = when (connectionState) { + // Enable the refresh button if: + // 1. Pump is not connected (to be able to manually initiate a pump status update) + // 2. Pump is suspended (in case the user resumed the pump and wants to update the status in AAPS) + // 3. When an error happened (to manually clear the pumpErrorObserved flag and unlock the loop after dealing with the error) ComboV2Plugin.DriverState.Disconnected, - ComboV2Plugin.DriverState.Suspended -> true + ComboV2Plugin.DriverState.Suspended, + ComboV2Plugin.DriverState.Error-> true else -> false } diff --git a/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/ComboV2Plugin.kt b/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/ComboV2Plugin.kt index 9e595f2d33..fd853783a1 100644 --- a/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/ComboV2Plugin.kt +++ b/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/ComboV2Plugin.kt @@ -12,6 +12,7 @@ import dagger.android.HasAndroidInjector import info.nightscout.comboctl.android.AndroidBluetoothInterface import info.nightscout.comboctl.base.BasicProgressStage import info.nightscout.comboctl.base.BluetoothException +import info.nightscout.comboctl.base.BluetoothNotEnabledException import info.nightscout.comboctl.base.ComboException import info.nightscout.comboctl.base.DisplayFrame import info.nightscout.comboctl.base.NullDisplayFrame @@ -65,7 +66,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async -import kotlinx.coroutines.cancel import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel @@ -134,8 +134,8 @@ class ComboV2Plugin @Inject constructor ( // Coroutine scope and the associated job. All coroutines // that are started in this plugin are part of this scope. - private val pumpCoroutineMainJob = SupervisorJob() - private val pumpCoroutineScope = CoroutineScope(Dispatchers.Default + pumpCoroutineMainJob) + private var pumpCoroutineScopeJob = SupervisorJob() + private var pumpCoroutineScope = CoroutineScope(Dispatchers.Default + pumpCoroutineScopeJob) private val _pumpDescription = PumpDescription() @@ -189,6 +189,18 @@ class ComboV2Plugin @Inject constructor ( // allow other components to react to state changes. private val _driverStateFlow = MutableStateFlow(DriverState.NotInitialized) + // If true, the pump was found to be suspended during the connect() + // call. This is separate from driverStateFlow and driverStateUIFlow. + // It is set immediately after connect() (while the other two may be + // set in a separate coroutine), which is important for check + // inside connect() and checks before commands like deliverTreatment() + // are run. This is what drives the isSuspended() call, and is _not_ + // to be used for UI update purposes (use driverStateUIFlow for that). + // Like driverStateUIFlow, this state persists even after disconnecting + // from the pump. This is necessary, because AAPS may call isSuspended() + // even when the pump is not connected. + private var pumpIsSuspended = false + // The basal profile that is set to be the pump's current profile. // If the pump's actual basal profile deviates from this, it is // overwritten. This check is performed in checkBasalProfile(). @@ -225,15 +237,19 @@ class ComboV2Plugin @Inject constructor ( // Driver is connected, but pump is suspended and // cannot currently execute commands. This state is // special in that it technically persists even after - // disconnecting. However, it is still important to - // model it as a driver state to prevent commands - // that deliver insulin from being executed (and, - // it is needed for the isSuspended() implementation). - // NOTE: Instead of comparing the driverStateFlow - // value with this state directly, consider using - // isSuspended() instead, since it is based on the - // driverStateUIFlow, and thus retains the Suspended - // and Error states even after disconnecting. + // disconnecting (because the pump remains suspended + // until the user resumes it, not until the connection + // is terminated), but it does not persists the same + // way here (it is replaced by Disconnected after + // the connection is terminated). This state is used + // for UI updates (see driverStateUIFlow) and for + // checks during driver state updates and connection + // attempts. + // NOTE: Do not compare against this state to check + // prior to commands like deliverTreatment() if + // the pump is currently suspended or not. Use + // isSuspended() instead. See the pumpIsSuspended + // documentation for details. object Suspended : DriverState("suspended") // Driver is currently executing a command. // isBusy() will return true in this state. @@ -255,6 +271,8 @@ class ComboV2Plugin @Inject constructor ( } override fun onStart() { + aapsLogger.info(LTag.PUMP, "Starting combov2 driver") + super.onStart() updateComboCtlLogLevel() @@ -288,51 +306,86 @@ class ComboV2Plugin @Inject constructor ( } aapsLogger.debug(LTag.PUMP, "Creating bluetooth interface") - bluetoothInterface = AndroidBluetoothInterface(context) + val newBluetoothInterface = AndroidBluetoothInterface(context) + bluetoothInterface = newBluetoothInterface + + aapsLogger.info(LTag.PUMP, "Continuing combov2 driver start in coroutine") // Continue initialization in a separate coroutine. This allows us to call // runWithPermissionCheck(), which will keep trying to run the code block // until either the necessary Bluetooth permissions are granted, or the // coroutine is cancelled (see onStop() below). pumpCoroutineScope.launch { - runWithPermissionCheck( - context, config, aapsLogger, androidPermission, - permissionsToCheckFor = listOf("android.permission.BLUETOOTH_CONNECT") - ) { - aapsLogger.debug(LTag.PUMP, "Setting up bluetooth interface") - bluetoothInterface!!.setup() + try { + runWithPermissionCheck( + context, config, aapsLogger, androidPermission, + permissionsToCheckFor = listOf("android.permission.BLUETOOTH_CONNECT") + ) { + aapsLogger.debug(LTag.PUMP, "Setting up bluetooth interface") - aapsLogger.debug(LTag.PUMP, "Setting up pump manager") - pumpManager = ComboCtlPumpManager(bluetoothInterface!!, pumpStateStore) - pumpManager!!.setup { - _pairedStateUIFlow.value = false - unpairing = false + try { + newBluetoothInterface.setup() + + rxBus.send(EventDismissNotification(Notification.BLUETOOTH_NOT_ENABLED)) + + aapsLogger.debug(LTag.PUMP, "Setting up pump manager") + val newPumpManager = ComboCtlPumpManager(newBluetoothInterface, pumpStateStore) + newPumpManager.setup { + _pairedStateUIFlow.value = false + unpairing = false + } + + // UI flows that must have defined values right + // at start are initialized here. + + // The paired state UI flow is special in that it is also + // used as the backing store for the isPaired() function, + // so setting up that UI state flow equals updating that + // paired state. + val paired = newPumpManager.getPairedPumpAddresses().isNotEmpty() + _pairedStateUIFlow.value = paired + + pumpManager = newPumpManager + } catch (_: BluetoothNotEnabledException) { + uiInteraction.addNotification( + Notification.BLUETOOTH_NOT_ENABLED, + text = rh.gs(info.nightscout.core.ui.R.string.ble_not_enabled), + level = Notification.INFO + ) + + // If the user currently has Bluetooth disabled, retry until + // the user turns it on. AAPS will automatically show a dialog + // box which requests the user to enable Bluetooth. Upon + // catching this exception, runWithPermissionCheck() will wait + // a bit before retrying, so no delay() call is needed here. + throw RetryPermissionCheckException() + } + + setDriverState(DriverState.Disconnected) + + aapsLogger.info(LTag.PUMP, "combov2 driver start complete") + + // NOTE: EventInitializationChanged is sent in getPumpStatus() . } - - // UI flows that must have defined values right - // at start are initialized here. - - // The paired state UI flow is special in that it is also - // used as the backing store for the isPaired() function, - // so setting up that UI state flow equals updating that - // paired state. - val paired = pumpManager!!.getPairedPumpAddresses().isNotEmpty() - _pairedStateUIFlow.value = paired - - setDriverState(DriverState.Disconnected) - - // NOTE: EventInitializationChanged is sent in getPumpStatus() . + } catch (e: CancellationException) { + aapsLogger.info(LTag.PUMP, "combov2 driver start cancelled") + throw e } } } override fun onStop() { - // Cancel any ongoing background coroutines. This includes an ongoing - // unfinished initialization that still waits for the user to grant - // Bluetooth permissions. - pumpCoroutineScope.cancel() + aapsLogger.info(LTag.PUMP, "Stopping combov2 driver") runBlocking { + // Cancel any ongoing background coroutines. This includes an ongoing + // unfinished initialization that still waits for the user to grant + // Bluetooth permissions. Also join to wait for the coroutines to + // finish. Otherwise, race conditions can occur, for example, when + // a coroutine tries to access bluetoothInterface right after it + // was torn down below. + pumpCoroutineScopeJob.cancelAndJoin() + // Normally this should not happen, but to be safe, // make sure any running pump instance is disconnected. pump?.disconnect() @@ -353,7 +406,13 @@ class ComboV2Plugin @Inject constructor ( rxBus.send(EventInitializationChanged()) initializationChangedEventSent = false + // The old job and scope were completed. We need new ones. + pumpCoroutineScopeJob = SupervisorJob() + pumpCoroutineScope = CoroutineScope(Dispatchers.Default + pumpCoroutineScopeJob) + super.onStop() + + aapsLogger.info(LTag.PUMP, "combov2 driver stopped") } override fun preprocessPreferences(preferenceFragment: PreferenceFragmentCompat) { @@ -410,12 +469,7 @@ class ComboV2Plugin @Inject constructor ( override fun isInitialized(): Boolean = isPaired() && (driverStateFlow.value != DriverState.NotInitialized) && !pumpErrorObserved - override fun isSuspended(): Boolean = - when (driverStateUIFlow.value) { - DriverState.Suspended, - DriverState.Error -> true - else -> false - } + override fun isSuspended(): Boolean = pumpIsSuspended override fun isBusy(): Boolean = when (driverStateFlow.value) { @@ -509,18 +563,18 @@ class ComboV2Plugin @Inject constructor ( } try { - runBlocking { - pump = pumpManager?.acquirePump(bluetoothAddress, activeBasalProfile) { event -> handlePumpEvent(event) } + val curPumpManager = pumpManager ?: throw Error("Could not get pump manager; this should not happen. Please report this as a bug.") + + val acquiredPump = runBlocking { + curPumpManager.acquirePump(bluetoothAddress, activeBasalProfile) { event -> handlePumpEvent(event) } } - if (pump == null) { - aapsLogger.error(LTag.PUMP, "Could not get pump instance - pump state store may be corrupted") - unpairDueToPumpDataError() - return - } + pump = acquiredPump _bluetoothAddressUIFlow.value = bluetoothAddress.toString() - _serialNumberUIFlow.value = pumpManager!!.getPumpID(bluetoothAddress) + _serialNumberUIFlow.value = curPumpManager.getPumpID(bluetoothAddress) + + rxBus.send(EventDismissNotification(Notification.BLUETOOTH_NOT_ENABLED)) // Erase any display frame that may be left over from a previous connection. @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) @@ -528,7 +582,7 @@ class ComboV2Plugin @Inject constructor ( stateAndStatusFlowsDeferred = pumpCoroutineScope.async { coroutineScope { - pump!!.stateFlow + acquiredPump.stateFlow .onEach { pumpState -> val driverState = when (pumpState) { // The Disconnected pump state is ignored, since the Disconnected @@ -549,7 +603,7 @@ class ComboV2Plugin @Inject constructor ( setDriverState(driverState) } .launchIn(this) - pump!!.statusFlow + acquiredPump.statusFlow .onEach { newPumpStatus -> if (newPumpStatus == null) return@onEach @@ -571,7 +625,7 @@ class ComboV2Plugin @Inject constructor ( rxBus.send(EventRefreshOverview("ComboV2 pump status updated")) } .launchIn(this) - pump!!.lastBolusFlow + acquiredPump.lastBolusFlow .onEach { lastBolus -> if (lastBolus == null) return@onEach @@ -579,7 +633,7 @@ class ComboV2Plugin @Inject constructor ( _lastBolusUIFlow.value = lastBolus } .launchIn(this) - pump!!.currentTbrFlow + acquiredPump.currentTbrFlow .onEach { currentTbr -> _currentTbrUIFlow.value = currentTbr } @@ -587,7 +641,7 @@ class ComboV2Plugin @Inject constructor ( } } - setupUiFlows() + setupUiFlows(acquiredPump) //// // The actual connect procedure begins here. @@ -613,6 +667,14 @@ class ComboV2Plugin @Inject constructor ( // No need to set the driver state here, since the pump's stateFlow will announce that. pump?.let { + pumpIsSuspended = when (it.stateFlow.value) { + ComboCtlPump.State.Suspended, + is ComboCtlPump.State.Error -> true + else -> false + } + + aapsLogger.debug(LTag.PUMP, "Pump is suspended: $pumpIsSuspended") + // We can't read the active profile number in the suspended state, since // the Combo's screen does not show any profile number then. if (!isSuspended()) { @@ -696,6 +758,12 @@ class ComboV2Plugin @Inject constructor ( executePendingDisconnect() } } + } catch (_: BluetoothNotEnabledException) { + uiInteraction.addNotification( + Notification.BLUETOOTH_NOT_ENABLED, + text = rh.gs(info.nightscout.core.ui.R.string.ble_not_enabled), + level = Notification.INFO + ) } catch (e: Exception) { aapsLogger.error(LTag.PUMP, "Connection failure: $e") ToastUtils.showToastInUiThread(context, rh.gs(R.string.combov2_could_not_connect)) @@ -766,6 +834,8 @@ class ComboV2Plugin @Inject constructor ( } } + val acquiredPump = getAcquiredPump() + rxBus.send(EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)) rxBus.send(EventDismissNotification(Notification.FAILED_UPDATE_PROFILE)) @@ -777,7 +847,7 @@ class ComboV2Plugin @Inject constructor ( runBlocking { try { executeCommand { - if (pump!!.setBasalProfile(requestedBasalProfile)) { + if (acquiredPump.setBasalProfile(requestedBasalProfile)) { aapsLogger.debug(LTag.PUMP, "Basal profiles are different; new profile set") activeBasalProfile = requestedBasalProfile updateBaseBasalRateUI() @@ -872,7 +942,6 @@ class ComboV2Plugin @Inject constructor ( pumpSync.insertTherapyEventIfNewWithTimestamp( timestamp = System.currentTimeMillis(), type = DetailedBolusInfo.EventType.INSULIN_CHANGE, - note = rh.gs(R.string.combov2_note_reservoir_change), pumpId = null, pumpType = PumpType.ACCU_CHEK_COMBO, pumpSerial = serialNumber() @@ -897,7 +966,6 @@ class ComboV2Plugin @Inject constructor ( pumpSync.insertTherapyEventIfNewWithTimestamp( timestamp = System.currentTimeMillis(), type = DetailedBolusInfo.EventType.PUMP_BATTERY_CHANGE, - note = rh.gs(R.string.combov2_note_battery_change), pumpId = null, pumpType = PumpType.ACCU_CHEK_COMBO, pumpSerial = serialNumber() @@ -927,6 +995,8 @@ class ComboV2Plugin @Inject constructor ( // (Also, a zero insulin value makes no sense when bolusing.) require((detailedBolusInfo.insulin > 0) && (detailedBolusInfo.carbs <= 0.0)) { detailedBolusInfo.toString() } + val acquiredPump = getAcquiredPump() + val requestedBolusAmount = detailedBolusInfo.insulin.iuToCctlBolus() val bolusReason = when (detailedBolusInfo.bolusType) { DetailedBolusInfo.BolusType.NORMAL -> ComboCtlPump.StandardBolusReason.NORMAL @@ -960,7 +1030,7 @@ class ComboV2Plugin @Inject constructor ( ) val bolusProgressJob = pumpCoroutineScope.launch { - pump!!.bolusDeliveryProgressFlow + acquiredPump.bolusDeliveryProgressFlow .collect { progressReport -> when (progressReport.stage) { is RTCommandProgressStage.DeliveringBolus -> { @@ -983,14 +1053,16 @@ class ComboV2Plugin @Inject constructor ( // Run the delivery in a sub-coroutine to be able // to cancel it via stopBolusDelivering(). val newBolusJob = pumpCoroutineScope.async { - // Store a local reference to the Pump instance. "pump" - // is set to null in case of an error, because then, - // disconnectInternal() is called (which sets pump to null). - // However, we still need to access the last delivered bolus - // from the pump's lastBolusFlow, even if an error happened. - // Solve this by storing this reference and accessing the - // lastBolusFlow through it. - val acquiredPump = pump!! + // NOTE: Above, we take a local reference to the acquired Pump instance, + // with a check that throws an exception in case the "pump" member is + // null. This local reference is particularly important inside this + // coroutine, because the "pump" member is set to null in case of an + // error or other disconnect reason (see disconnectInternal()). However, + // we still need to access the last delivered bolus inside this coroutine + // from the pump's lastBolusFlow, even if an error happened. Accessing + // it through the "pump" member would then result in an NPE. This is + // solved by instead accessing the lastBolusFlow through the local + // "acquiredPump" reference. try { executeCommand { @@ -1170,11 +1242,13 @@ class ComboV2Plugin @Inject constructor ( return } + val acquiredPump = getAcquiredPump() + runBlocking { try { executeCommand { - val tbrComment = when (pump!!.setTbr(percentage, durationInMinutes, tbrType, force100Percent)) { + val tbrComment = when (acquiredPump.setTbr(percentage, durationInMinutes, tbrType, force100Percent)) { ComboCtlPump.SetTbrOutcome.SET_NORMAL_TBR -> rh.gs(R.string.combov2_setting_tbr_succeeded) ComboCtlPump.SetTbrOutcome.SET_EMULATED_100_TBR -> @@ -1327,8 +1401,9 @@ class ComboV2Plugin @Inject constructor ( override fun serialNumber(): String { val bluetoothAddress = getBluetoothAddress() - return if ((bluetoothAddress != null) && (pumpManager != null)) - pumpManager!!.getPumpID(bluetoothAddress) + val curPumpManager = pumpManager + return if ((bluetoothAddress != null) && (curPumpManager != null)) + curPumpManager.getPumpID(bluetoothAddress) else rh.gs(R.string.combov2_not_paired) } @@ -1396,6 +1471,7 @@ class ComboV2Plugin @Inject constructor ( override fun loadTDDs(): PumpEnactResult { val pumpEnactResult = PumpEnactResult(injector) + val acquiredPump = getAcquiredPump() runBlocking { try { @@ -1403,7 +1479,7 @@ class ComboV2Plugin @Inject constructor ( val tddMap = mutableMapOf() executeCommand { - val tddHistory = pump!!.fetchTDDHistory() + val tddHistory = acquiredPump.fetchTDDHistory() tddHistory .filter { it.totalDailyAmount >= 1 } @@ -1544,15 +1620,24 @@ class ComboV2Plugin @Inject constructor ( context, config, aapsLogger, androidPermission, permissionsToCheckFor = listOf("android.permission.BLUETOOTH_CONNECT") ) { - pumpManager?.pairWithNewPump(discoveryDuration) { newPumpAddress, previousAttemptFailed -> - aapsLogger.info( - LTag.PUMP, - "New pairing PIN request from Combo pump with Bluetooth " + - "address $newPumpAddress (previous attempt failed: $previousAttemptFailed)" - ) - _previousPairingAttemptFailedFlow.value = previousAttemptFailed - newPINChannel.receive() - } ?: throw IllegalStateException("Attempting to access uninitialized pump manager") + try { + pumpManager?.pairWithNewPump(discoveryDuration) { newPumpAddress, previousAttemptFailed -> + aapsLogger.info( + LTag.PUMP, + "New pairing PIN request from Combo pump with Bluetooth " + + "address $newPumpAddress (previous attempt failed: $previousAttemptFailed)" + ) + _previousPairingAttemptFailedFlow.value = previousAttemptFailed + newPINChannel.receive() + } ?: throw IllegalStateException("Attempting to access uninitialized pump manager") + } catch (e: BluetoothNotEnabledException) { + // If Bluetooth is turned off during pairing, show a toaster message. + // Notifications on the AAPS overview fragment are not useful here + // because the pairing activity obscures that fragment. So, instead, + // alert the user by showing the notification via the toaster. + ToastUtils.errorToast(context, info.nightscout.core.ui.R.string.ble_not_enabled) + ComboCtlPumpManager.PairingResult.ExceptionDuringPairing(e) + } } if (pairingResult !is ComboCtlPumpManager.PairingResult.Success) @@ -1663,6 +1748,12 @@ class ComboV2Plugin @Inject constructor ( // This is a variant of driverStateFlow that retains the Error // and Suspended state even after disconnecting to make sure these // states kept being showed to the user post-disconnect. + // NOTE: Do not rely on this to check prior to a command if the + // pump is suspended or not, since the driver state UI flow is + // updated in a separate coroutine, and is _only_ meant for UI + // updates. Using this for other purposes can cause race conditions + // to appear, such as when immediately after the Pump.connect() call + // finishes, the state is checked. Use isSuspended() instead. private val _driverStateUIFlow = MutableStateFlow(DriverState.NotInitialized) val driverStateUIFlow = _driverStateUIFlow.asStateFlow() @@ -1716,11 +1807,11 @@ class ComboV2Plugin @Inject constructor ( /*** Misc private functions ***/ - private fun setupUiFlows() { + private fun setupUiFlows(acquiredPump: ComboCtlPump) { pumpUIFlowsDeferred = pumpCoroutineScope.async { try { coroutineScope { - pump!!.connectProgressFlow + acquiredPump.connectProgressFlow .onEach { progressReport -> val description = when (val progStage = progressReport.stage) { is BasicProgressStage.EstablishingBtConnection -> @@ -1738,7 +1829,7 @@ class ComboV2Plugin @Inject constructor ( } .launchIn(this) - pump!!.setDateTimeProgressFlow + acquiredPump.setDateTimeProgressFlow .onEach { progressReport -> val description = when (progressReport.stage) { RTCommandProgressStage.SettingDateTimeHour, @@ -1755,7 +1846,7 @@ class ComboV2Plugin @Inject constructor ( } .launchIn(this) - pump!!.getBasalProfileFlow + acquiredPump.getBasalProfileFlow .onEach { progressReport -> val description = when (val stage = progressReport.stage) { is RTCommandProgressStage.GettingBasalProfile -> @@ -1769,7 +1860,7 @@ class ComboV2Plugin @Inject constructor ( } .launchIn(this) - pump!!.setBasalProfileFlow + acquiredPump.setBasalProfileFlow .onEach { progressReport -> val description = when (val stage = progressReport.stage) { is RTCommandProgressStage.SettingBasalProfile -> @@ -1783,7 +1874,7 @@ class ComboV2Plugin @Inject constructor ( } .launchIn(this) - pump!!.bolusDeliveryProgressFlow + acquiredPump.bolusDeliveryProgressFlow .onEach { progressReport -> val description = when (val stage = progressReport.stage) { is RTCommandProgressStage.DeliveringBolus -> @@ -1801,7 +1892,7 @@ class ComboV2Plugin @Inject constructor ( } .launchIn(this) - pump!!.parsedDisplayFrameFlow + acquiredPump.parsedDisplayFrameFlow .onEach { parsedDisplayFrame -> _displayFrameUIFlow.emit( parsedDisplayFrame?.displayFrame ?: NullDisplayFrame @@ -2003,7 +2094,11 @@ class ComboV2Plugin @Inject constructor ( // It makes no sense to reach this location with pump // being null due to the checks above. - assert(pump != null) + val pumpToDisconnect = pump + if (pumpToDisconnect == null) { + aapsLogger.error(LTag.PUMP, "Current pump is already null") + return + } // Run these operations in a coroutine to be able to wait // until the disconnect really completes and the UI flows @@ -2041,17 +2136,17 @@ class ComboV2Plugin @Inject constructor ( // the Pump.disconnect() call shuts down the RFCOMM socket, // making all send/receive calls fail. - if (pump!!.stateFlow.value == ComboCtlPump.State.Connecting) { + if (pumpToDisconnect.stateFlow.value == ComboCtlPump.State.Connecting) { // Case #1 from above aapsLogger.debug(LTag.PUMP, "Cancelling ongoing connect attempt") connectionSetupJob?.cancel() - pump?.disconnect() + pumpToDisconnect.disconnect() connectionSetupJob?.join() } else { // Case #2 from above aapsLogger.debug(LTag.PUMP, "Disconnecting Combo (if not disconnected already by a cancelling request)") connectionSetupJob?.cancelAndJoin() - pump?.disconnect() + pumpToDisconnect.disconnect() } aapsLogger.debug(LTag.PUMP, "Combo disconnected; cancelling UI flows coroutine") @@ -2284,6 +2379,8 @@ class ComboV2Plugin @Inject constructor ( private fun getBluetoothAddress(): ComboCtlBluetoothAddress? = pumpManager?.getPairedPumpAddresses()?.firstOrNull() + private fun getAcquiredPump() = pump ?: throw Error("There is no currently acquired pump; this should not happen. Please report this as a bug.") + private fun isDisconnected() = when (driverStateFlow.value) { DriverState.NotInitialized, diff --git a/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/Utility.kt b/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/Utility.kt index 03576e4401..407a8ecf06 100644 --- a/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/Utility.kt +++ b/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/Utility.kt @@ -3,6 +3,7 @@ package info.nightscout.pump.combov2 import android.content.Context import android.os.Build import info.nightscout.comboctl.android.AndroidBluetoothPermissionException +import info.nightscout.comboctl.base.ComboException import info.nightscout.comboctl.main.BasalProfile import info.nightscout.comboctl.main.NUM_COMBO_BASAL_PROFILE_FACTORS import info.nightscout.interfaces.AndroidPermission @@ -32,7 +33,37 @@ fun AAPSProfile.toComboCtlBasalProfile(): BasalProfile { return BasalProfile(factors) } -suspend fun runWithPermissionCheck( +internal class RetryPermissionCheckException : ComboException("retry permission check") + +// Utility function to perform Android permission checks before running a block. +// If the permissions are not given, wait for a little while, then retry. +// This is needed for AAPS<->combov2 integration, since AAPS can start combov2 +// even _before_ the user granted AAPS BLUETOOTH_CONNECT etc. permissions. +// +// permissionsToCheckFor is a collection of permissions strings like +// Manifest.permission.BLUETOOTH_SCAN. The function goes through the collection, +// and checks each and every permission to see if they have all been granted. +// Only if all have been granted will the block be executed. +// +// It is possible that within the block, some additional permission checks +// are performed - in particular, these can be checks for permissions that +// weren't part of the permissionsToCheckFor collection. If such a permission +// is not granted, the block can throw AndroidBluetoothPermissionException. +// That exception also specifies what exact permissions haven't been granted +// (yet). runWithPermissionCheck() then adds these missing permissions to +// permissionsToCheckFor, and tries its permission check again, this time +// with these extra permissions included. That way, a failed permission +// check within the block does not break anything, and instead, these +// permissions too are re-checked by the same logic as the one that looks +// at the initially specified permissions. +// +// Additionally, the block might perform other checks that are not directly +// permissions but related to them. One example is a check to see if the +// Bluetooth adapter is enabled in addition to checking for Bluetooth +// permissions. When such custom checks fail, they can throw +// RetryPermissionCheckException to inform this function that it should +// retry its run, just as if a permission hadn't been granted. +internal suspend fun runWithPermissionCheck( context: Context, config: Config, aapsLogger: AAPSLogger, @@ -53,12 +84,20 @@ suspend fun runWithPermissionCheck( } if (notAllPermissionsGranted) { - delay(1000) // Wait a little bit before retrying to avoid 100% CPU usage + // Wait a little bit before retrying to avoid 100% CPU usage. + // It is currently unknown if there is a way to instead get + // some sort of event from Android to inform that the permissions + // have now been granted, so we have to resort to polling, + // at least for now. + delay(1000) continue } } return block.invoke() + } catch (e: RetryPermissionCheckException) { + // See the above delay() call, which fulfills the exact same purpose. + delay(1000) } catch (e: AndroidBluetoothPermissionException) { permissions = permissionsToCheckFor union e.missingPermissions } diff --git a/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/activities/ComboV2PairingActivity.kt b/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/activities/ComboV2PairingActivity.kt index 44daeb1f0f..8ecbda0c7a 100644 --- a/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/activities/ComboV2PairingActivity.kt +++ b/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/activities/ComboV2PairingActivity.kt @@ -1,18 +1,24 @@ package info.nightscout.pump.combov2.activities +import android.Manifest import android.app.Activity +import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.view.View +import androidx.activity.ComponentActivity +import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat import androidx.databinding.DataBindingUtil import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import dagger.android.support.DaggerAppCompatActivity import info.nightscout.comboctl.base.BasicProgressStage import info.nightscout.comboctl.base.PairingPIN +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.ui.dialogs.OKDialog import info.nightscout.pump.combov2.ComboV2Plugin import info.nightscout.pump.combov2.R @@ -20,42 +26,65 @@ import info.nightscout.pump.combov2.databinding.Combov2PairingActivityBinding import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.LTag import info.nightscout.shared.interfaces.ResourceHelper +import kotlinx.coroutines.CompletableJob +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import javax.inject.Inject -class ComboV2PairingActivity : DaggerAppCompatActivity() { +// A counterpart to BlePreCheckImpl that is designed for coroutines. +private class BluetoothPermissionChecks( + private val activity: ComponentActivity, + private val permissions: List, + private val aapsLogger: AAPSLogger +) { + private val activityResultLauncher: ActivityResultLauncher> + private var waitForCompletion: CompletableJob? = null + + init { + activityResultLauncher = activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { + waitForCompletion?.complete() + } + } + + suspend fun requestAndCheck() { + val missingPermissions = permissions + .filter { + ContextCompat.checkSelfPermission(activity, it) != PackageManager.PERMISSION_GRANTED + } + .toTypedArray() + + if (missingPermissions.isEmpty()) + return + + aapsLogger.debug(LTag.PUMP, "Missing permissions: " + missingPermissions.joinToString(", ")) + + waitForCompletion = Job() + activityResultLauncher.launch(missingPermissions) + waitForCompletion?.join() + waitForCompletion = null + } + + fun unregister() { + activityResultLauncher.unregister() + } +} + +class ComboV2PairingActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var rh: ResourceHelper @Inject lateinit var combov2Plugin: ComboV2Plugin + private var uiInitialized = false + private var unregisterActivityLauncher = {} + private var bluetoothPermissionChecks: BluetoothPermissionChecks? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val binding: Combov2PairingActivityBinding = DataBindingUtil.setContentView( - this, R.layout.combov2_pairing_activity) - - // In the NotInitialized state, the PumpManager is unavailable because it cannot - // function without Bluetooth permissions. Several of ComboV2Plugin's functions - // such as getPairingProgressFlow() depend on PumpManager though. To prevent UI - // controls from becoming active without having a PumpManager, show instead a - // view on the activity that explains why pairing is currently not possible. - if (combov2Plugin.driverStateUIFlow.value == ComboV2Plugin.DriverState.NotInitialized) { - aapsLogger.info(LTag.PUMP, "Cannot pair right now; disabling pairing UI controls, showing message instead") - - binding.combov2PairingSectionInitial.visibility = View.GONE - binding.combov2PairingSectionCannotPairDriverNotInitialized.visibility = View.VISIBLE - - binding.combov2CannotPairGoBack.setOnClickListener { - finish() - } - - return - } - // Install an activity result caller for when the user presses // "deny" or "reject" in the dialog that pops up when Android // asks for permission to enable device discovery. In such a @@ -74,7 +103,131 @@ class ComboV2PairingActivity : DaggerAppCompatActivity() { combov2Plugin.customDiscoveryActivityStartCallback = { intent -> startPairingActivityLauncher.launch(intent) } + unregisterActivityLauncher = { + startPairingActivityLauncher.unregister() + } + val binding: Combov2PairingActivityBinding = DataBindingUtil.setContentView( + this, R.layout.combov2_pairing_activity) + + val thisActivity = this + + // Set the pairing sections to initially show the "not initialized" one + // in case the Bluetooth permissions haven't been granted yet by the user. + binding.combov2PairingSectionInitial.visibility = View.GONE + binding.combov2PairingSectionCannotPairDriverNotInitialized.visibility = View.VISIBLE + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // Launch the BluetoothPermissionChecks in the CREATED lifecycle state. + // This is important, because registering an activity (which the + // BluetoothPermissionChecks class does) must take place _before_ the + // STARTED state is reached. + lifecycleScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) { + aapsLogger.debug(LTag.PUMP, "Creating and registering BT permissions check object") + bluetoothPermissionChecks = BluetoothPermissionChecks( + thisActivity, + listOf( + Manifest.permission.BLUETOOTH_SCAN, + Manifest.permission.BLUETOOTH_CONNECT + ), + aapsLogger + ) + } + } + + // Unregister any activity that BluetoothPermissionChecks previously + // registered if this pairing activity is getting destroyed. + lifecycleScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.DESTROYED) { + aapsLogger.debug(LTag.PUMP, "Unregistering BT permissions check object") + bluetoothPermissionChecks?.unregister() + bluetoothPermissionChecks = null + } + } + } + + lifecycleScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + bluetoothPermissionChecks?.let { + aapsLogger.debug(LTag.PUMP, "Requesting and checking BT permissions") + it.requestAndCheck() + } + combov2Plugin.driverStateUIFlow + .onEach { driverState -> + if (!uiInitialized) { + when (driverState) { + // In the NotInitialized state, the PumpManager is unavailable because it cannot + // function without Bluetooth permissions. Several of ComboV2Plugin's functions + // such as getPairingProgressFlow() depend on PumpManager though. To prevent UI + // controls from becoming active without having a PumpManager, show instead a + // view on the activity that explains why pairing is currently not possible. + ComboV2Plugin.DriverState.NotInitialized -> { + aapsLogger.info(LTag.PUMP, "Cannot pair right now; disabling pairing UI controls, showing message instead") + + binding.combov2PairingSectionInitial.visibility = View.GONE + binding.combov2PairingSectionCannotPairDriverNotInitialized.visibility = View.VISIBLE + + binding.combov2CannotPairGoBack.setOnClickListener { + finish() + } + } + + else -> { + binding.combov2PairingSectionCannotPairDriverNotInitialized.visibility = View.GONE + setupUi(binding) + uiInitialized = true + } + } + } + } + .launchIn(this) + } + } + } + + override fun onBackPressed() { + aapsLogger.info(LTag.PUMP, "User pressed the back button; cancelling any ongoing pairing") + combov2Plugin.cancelPairing() + @Suppress("DEPRECATION") + super.onBackPressed() + } + + override fun onDestroy() { + // In the NotInitialized state, getPairingProgressFlow() crashes because there + // is no PumpManager present. But in that state, the pairing progress flow needs + // no reset because no pairing can happen in that state anyway. + if (combov2Plugin.driverStateUIFlow.value != ComboV2Plugin.DriverState.NotInitialized) { + // Reset the pairing progress reported to allow for future pairing attempts. + // Do this only after pairing was finished or aborted. onDestroy() can be + // called in the middle of a pairing process, and we do not want to reset + // the progress reporter mid-pairing. + when (combov2Plugin.getPairingProgressFlow().value.stage) { + BasicProgressStage.Finished, + is BasicProgressStage.Aborted -> { + aapsLogger.debug( + LTag.PUMP, + "Resetting pairing progress reporter after pairing was finished/aborted" + ) + combov2Plugin.resetPairingProgress() + } + + else -> Unit + } + } + + // Remove the activity start callback and unregister the activity + // launcher to make sure that future registerForActivityResult() + // calls start from a blank slate. (This is about the discovery + // activity, not about the BluetoothPermissionChecks ones.) + combov2Plugin.customDiscoveryActivityStartCallback = null + unregisterActivityLauncher.invoke() + unregisterActivityLauncher = {} + + super.onDestroy() + } + + private fun setupUi(binding: Combov2PairingActivityBinding) { binding.combov2PairingFinishedOk.setOnClickListener { finish() } @@ -197,112 +350,75 @@ class ComboV2PairingActivity : DaggerAppCompatActivity() { }) } - lifecycleScope.launch { - lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - combov2Plugin.getPairingProgressFlow() - .onEach { progressReport -> - val stage = progressReport.stage + combov2Plugin.getPairingProgressFlow() + .onEach { progressReport -> + val stage = progressReport.stage - binding.combov2PairingSectionInitial.visibility = - if (stage == BasicProgressStage.Idle) View.VISIBLE else View.GONE - binding.combov2PairingSectionFinished.visibility = - if (stage == BasicProgressStage.Finished) View.VISIBLE else View.GONE - binding.combov2PairingSectionAborted.visibility = - if (stage is BasicProgressStage.Aborted) View.VISIBLE else View.GONE - binding.combov2PairingSectionMain.visibility = when (stage) { - BasicProgressStage.Idle, - BasicProgressStage.Finished, - is BasicProgressStage.Aborted -> View.GONE - else -> View.VISIBLE - } - - if (stage is BasicProgressStage.Aborted) { - binding.combov2PairingAbortedReasonText.text = when (stage) { - is BasicProgressStage.Cancelled -> rh.gs(R.string.combov2_pairing_cancelled) - is BasicProgressStage.Timeout -> rh.gs(R.string.combov2_pairing_combo_scan_timeout_reached) - is BasicProgressStage.Error -> rh.gs(R.string.combov2_pairing_failed_due_to_error, stage.cause.toString()) - else -> rh.gs(R.string.combov2_pairing_aborted_unknown_reasons) - } - } - - binding.combov2CurrentPairingStepDesc.text = when (val progStage = stage) { - BasicProgressStage.ScanningForPumpStage -> - rh.gs(R.string.combov2_scanning_for_pump) - - is BasicProgressStage.EstablishingBtConnection -> { - rh.gs( - R.string.combov2_establishing_bt_connection, - progStage.currentAttemptNr - ) - } - - BasicProgressStage.PerformingConnectionHandshake -> - rh.gs(R.string.combov2_pairing_performing_handshake) - - BasicProgressStage.ComboPairingKeyAndPinRequested -> - rh.gs(R.string.combov2_pairing_pump_requests_pin) - - BasicProgressStage.ComboPairingFinishing -> - rh.gs(R.string.combov2_pairing_finishing) - - else -> "" - } - - if (stage == BasicProgressStage.ComboPairingKeyAndPinRequested) { - binding.combov2PinEntryUi.visibility = View.VISIBLE - } else - binding.combov2PinEntryUi.visibility = View.GONE - - // Scanning for the pump can take a long time and happens at the - // beginning, so set the progress bar to indeterminate during that - // time to show _something_ to the user. - binding.combov2PairingProgressBar.isIndeterminate = - (stage == BasicProgressStage.ScanningForPumpStage) - - binding.combov2PairingProgressBar.progress = (progressReport.overallProgress * 100).toInt() - } - .launchIn(this) - - combov2Plugin.previousPairingAttemptFailedFlow - .onEach { previousAttemptFailed -> - binding.combov2PinFailureIndicator.visibility = - if (previousAttemptFailed) View.VISIBLE else View.GONE - } - .launchIn(this) - } - } - } - - override fun onBackPressed() { - aapsLogger.info(LTag.PUMP, "User pressed the back button; cancelling any ongoing pairing") - combov2Plugin.cancelPairing() - @Suppress("DEPRECATION") - super.onBackPressed() - } - - override fun onDestroy() { - // In the NotInitialized state, getPairingProgressFlow() crashes because there - // is no PumpManager present. But in that state, the pairing progress flow needs - // no reset because no pairing can happen in that state anyway. - if (combov2Plugin.driverStateUIFlow.value != ComboV2Plugin.DriverState.NotInitialized) { - // Reset the pairing progress reported to allow for future pairing attempts. - // Do this only after pairing was finished or aborted. onDestroy() can be - // called in the middle of a pairing process, and we do not want to reset - // the progress reporter mid-pairing. - when (combov2Plugin.getPairingProgressFlow().value.stage) { - BasicProgressStage.Finished, - is BasicProgressStage.Aborted -> { - aapsLogger.debug( - LTag.PUMP, - "Resetting pairing progress reporter after pairing was finished/aborted" - ) - combov2Plugin.resetPairingProgress() + binding.combov2PairingSectionInitial.visibility = + if (stage == BasicProgressStage.Idle) View.VISIBLE else View.GONE + binding.combov2PairingSectionFinished.visibility = + if (stage == BasicProgressStage.Finished) View.VISIBLE else View.GONE + binding.combov2PairingSectionAborted.visibility = + if (stage is BasicProgressStage.Aborted) View.VISIBLE else View.GONE + binding.combov2PairingSectionMain.visibility = when (stage) { + BasicProgressStage.Idle, + BasicProgressStage.Finished, + is BasicProgressStage.Aborted -> View.GONE + else -> View.VISIBLE } - else -> Unit - } - } + if (stage is BasicProgressStage.Aborted) { + binding.combov2PairingAbortedReasonText.text = when (stage) { + is BasicProgressStage.Cancelled -> rh.gs(R.string.combov2_pairing_cancelled) + is BasicProgressStage.Timeout -> rh.gs(R.string.combov2_pairing_combo_scan_timeout_reached) + is BasicProgressStage.Error -> rh.gs(R.string.combov2_pairing_failed_due_to_error, stage.cause.toString()) + else -> rh.gs(R.string.combov2_pairing_aborted_unknown_reasons) + } + } - super.onDestroy() + binding.combov2CurrentPairingStepDesc.text = when (val progStage = stage) { + BasicProgressStage.ScanningForPumpStage -> + rh.gs(R.string.combov2_scanning_for_pump) + + is BasicProgressStage.EstablishingBtConnection -> { + rh.gs( + R.string.combov2_establishing_bt_connection, + progStage.currentAttemptNr + ) + } + + BasicProgressStage.PerformingConnectionHandshake -> + rh.gs(R.string.combov2_pairing_performing_handshake) + + BasicProgressStage.ComboPairingKeyAndPinRequested -> + rh.gs(R.string.combov2_pairing_pump_requests_pin) + + BasicProgressStage.ComboPairingFinishing -> + rh.gs(R.string.combov2_pairing_finishing) + + else -> "" + } + + if (stage == BasicProgressStage.ComboPairingKeyAndPinRequested) { + binding.combov2PinEntryUi.visibility = View.VISIBLE + } else + binding.combov2PinEntryUi.visibility = View.GONE + + // Scanning for the pump can take a long time and happens at the + // beginning, so set the progress bar to indeterminate during that + // time to show _something_ to the user. + binding.combov2PairingProgressBar.isIndeterminate = + (stage == BasicProgressStage.ScanningForPumpStage) + + binding.combov2PairingProgressBar.progress = (progressReport.overallProgress * 100).toInt() + } + .launchIn(lifecycleScope) + + combov2Plugin.previousPairingAttemptFailedFlow + .onEach { previousAttemptFailed -> + binding.combov2PinFailureIndicator.visibility = + if (previousAttemptFailed) View.VISIBLE else View.GONE + } + .launchIn(lifecycleScope) } } diff --git a/pump/combov2/src/main/res/layout/combov2_pairing_activity.xml b/pump/combov2/src/main/res/layout/combov2_pairing_activity.xml index f3c7664f6f..40a9ff7571 100644 --- a/pump/combov2/src/main/res/layout/combov2_pairing_activity.xml +++ b/pump/combov2/src/main/res/layout/combov2_pairing_activity.xml @@ -189,6 +189,8 @@ android:id="@+id/combov2_pin_entry_ui" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" android:layout_marginBottom="20dp" android:gravity="center_horizontal" android:orientation="vertical" @@ -210,7 +212,8 @@ Indirizzo bluetooth Avvia associazione Associazione Combo in corso + Passi per eseguire l\'associazione con il tuo Combo:\n\n +1. Sul tuo microinfusore, vai alle Impostazioni Bluetooth\n +2. Controlla se un dispositivo è già mostrato come associato; in tal caso, vai alla schermata del microinfusore \"Elimina dispositivo\" per eliminare/disassociare quel dispositivo\n +3. Vai alla schermata del microinfusore \"Aggiungi dispositivo\" e avvia l\'associazione sul microinfusore\n +4. Clicca sul tasto \"Avvia associazione\" qui sotto per avviare l\'associazione in AndroidAPS\n + Dopo un po \', il nome del telefono viene mostrato sullo schermo del micro; premi il tasto di CONFERMA per confermare.\n\n +Quando l\'associazione è stata completata con successo, conferma la fine dell\'associazione sul microinfusore e torna alla schermata principale del microinfusore premendo il tasto di CONFERMA due volte.\n\n +Se non viene stabilita alcuna connessione dopo più di ~5 minuti:\n\n +1. Premi Indietro o il tasto \"Annulla associazione\"\n +2. Annulla l\'associazione sul Combo (premere i tasti SOPRA e MENU allo stesso tempo per annullare l\'associazione)\n +3. Prova ad associare di nuovo Inserisci PIN Annulla associazione PIN a 10 cifre @@ -37,6 +48,8 @@ Associazione fallita a causa di un errore: %1$s Associazione interrotta per motivi sconosciuti Scansione micro + Connessione Bluetooth (tentativo n. %1$d) + Esecuzione dell\'associazione con il micro Il micro richiede un PIN a 10 cifre Fine associazione Nessuna connessione per %1$d minuti @@ -52,6 +65,7 @@ Ricezione profilo basale Impostazione profilo basale Impostazione TBR %1$d%% per %2$d minuti + Cancellazione TBR in esecuzione Erogazione bolo di %1$.1f U Recupero storico TDD Aggiornamento data e ora del micro @@ -60,17 +74,34 @@ Durata ricerca (in secondi) Abilita log verboso del Combo Ricezione profilo basale; %1$d record letto(i) + Impostazione profilo basale; %1$d record scritto(i) + Erogazione bolo (%1$.1f di %2$.1f U erogate) + Impossibile erogare il trattamento - il micro è sospeso Insulina insufficiente nel serbatoio Bolo cancellato Erogazione bolo fallita. Sembra che nessun bolo sia stato erogato. Per sicurezza, controlla il micro per evitare un doppio bolo e se è tutto ok, erogalo di nuovo. Come protezione da eventuali \"bug\", i boli non vengono ripetuti automaticamente. Bolo non erogato Impossibile accedere ai dati del micro; il micro deve essere associato di nuovo + Rilevate erogazioni bolo non contabilizzate. Cancellazione del bolo per motivi di sicurezza. + Profilo basale attivo errato; il profilo 1 deve essere quello attivo, non il profilo %1$d + Avviso del Combo non riconosciuto + Avviso del Combo %1$d%% (%2$d min rimanenti) %1$d%% (meno di 1 minuto rimanente) Caricamento TDD annullato + Recupero TDD fallito Livello batteria basso + Il livello del serbatoio del micro è basso + Impostazione TBR riuscita Impostazione TBR fallita + Imposta TBR 100% (emulato) + Lasciando finire il TBR 100% (emulato) in esecuzione + TBR 100%: ignorando la richiesta ridondante + Limite inatteso riscontrato durante la regolazione di TBR: la percentuale target era %1$d%%, raggiunto un limite a %1$d%% + Impossibile impostare un TBR assoluto se la velocità basale di base è zero Associa AndroidAPS e Android con un micro Accu-Chek Combo attualmente non associato + Disassocia AndroidAPS e Android dal micro Accu-Chek Combo attualmente associato + Un TBR sconosciuto è stato rilevato e fermato; percentuale: %1$d%%; durata rimanente: %2$s Errore connessione: %1$s Ultima conn.: %1$d min fa Avviso: %s @@ -89,6 +120,8 @@ Data e/o ora cambiata Ora legale (DST) iniziata Ora legale (DST) terminata + Impossibile connettersi al micro perché il micro ha segnalato un errore. L\'utente deve gestire l\'errore e quindi attendere 5 minuti o premere il tasto Aggiorna nella scheda del driver. + Aggiornamento dello stato del micro dopo che il micro ha segnalato un errore Torna indietro Impossibile eseguire l\'associazione perché il driver non è inizializzato. Questo accade tipicamente perché i permessi Bluetooth necessari non sono stati concessi. Torna indietro, concedi i permessi Bluetooth, quindi riprova ad associare. diff --git a/pump/combov2/src/main/res/values-iw-rIL/strings.xml b/pump/combov2/src/main/res/values-iw-rIL/strings.xml index eecdada32f..52187f91a7 100644 --- a/pump/combov2/src/main/res/values-iw-rIL/strings.xml +++ b/pump/combov2/src/main/res/values-iw-rIL/strings.xml @@ -59,7 +59,6 @@ מבצע פקודה קורא פרופיל בזאלי הגדרת פרופיל בזאלי - מגדיר בזאלי זמני %1$d% למשך %2$d דקות מבטל בזאלי זמני נוכחי מזריק בולוס %1$.1f יח\' טוען היסטוריית מינונים יומיים @@ -81,8 +80,7 @@ פרופיל בזאלי פעיל שגוי; על פרופיל מספר 1 להיות הפרופיל הפעיל, לא פרופיל מספר %1$d התראת משאבה בלתי מזוהה התראת Combo - %1$d%% (נותרו %2$d דקות) - %1$d% (נותרה פחות מדקה אחת) + %1$d%% (נותרה פחות מדקה אחת) טעינת המינונים היומיים בוטלה טעינת המינונים היומיים נכשלה סוללת המשאבה חלשה diff --git a/pump/combov2/src/main/res/values-lt-rLT/strings.xml b/pump/combov2/src/main/res/values-lt-rLT/strings.xml index 4b01e80fec..82301b1d9b 100644 --- a/pump/combov2/src/main/res/values-lt-rLT/strings.xml +++ b/pump/combov2/src/main/res/values-lt-rLT/strings.xml @@ -30,21 +30,21 @@ Vyksta Combo susiejimas Žingsniai, reikalingi susieti Jūsų Combo:\n\n 1. Pompoje nueikite į Bluetooth nustatymus\n -2. Patikrinkite, ar įrenginys jau yra rodomas kaip suporuotas; jei taip, eikite į pompos ekraną \"Delete device\" ištrinti/atporuoti tą įrenginį\n -3. Eikite į pompos ekraną \"Add device\" ir pradėkite poravimą pompoje\n -4. Paspauskite \"Pradėti poravimą\" žemiau pradėti poravimą AndroidAPS\n +2. Patikrinkite, ar yra susietų įrenginių; jei taip, eikite į \"Delete device\" ir ištrinkite/atsiekite visus įrenginius\n +3. Eikite į \"Add device\" ir pradėkite susiejimą pompoje\n +4. Paspauskite \"Pradėti susiejimą\" ir pradėkite susiejimą AndroidAPS\n Po kurio laiko telefono pavadinimas parodomas pompos ekrane; paspauskite CHECK patvirtinimui.\n\n -Kai poravimas sėkmingai baigtas, patvirtinkite baigtą poravimą pompoje ir grįžkite į pagrindinį pompos ekraną du kartus paspaudę CHECK mygtuką.\n\n -Jei po daugiau nei ~5 minučių sujungimas nebaigtas:\n\n -1. Paspauskite atgal arba \"Cancel Pairing\" mygtuką\n -2. Atšaukite poravimą Combo (vienu metu paspauskite UP ir MENU mygtukus atšaukti poravimą)\n -3. Bandykite suporuoti dar kartą +Kai susiejimas sėkmingai baigtas, patvirtinkite baigtą susiejimą pompoje ir grįžkite į pagrindinį pompos ekraną du kartus paspaudę CHECK mygtuką.\n\n +Jei po daugiau nei ~5 minučių ryšys neužmegztas:\n\n +1. Paspauskite BACK arba \"Cancel Pairing\" mygtuką\n +2. Atšaukite susiejimą Combo (vienu metu paspauskite UP ir MENU mygtukus susiejimui atšaukti)\n +3. Bandykite susieti dar kartą Įveskite PIN Atšaukti susiejimą 10 skaitmenų PIN - Combo sėkmingai prijungta - Combo prijungimą nutraukė vartotojas - Pasiektas Combo skanavimo laiko limitas + Combo sėkmingai susieta + Combo susiejimą nutraukė vartotojas + Combo skanavimo laikas baigėsi Susiejimas nepavyko dėl klaidos: %1$s Susiejimas nutrūko dėl nežinomos priežasties Ieškoma ryšio su pompa @@ -56,7 +56,7 @@ Jei po daugiau nei ~5 minučių sujungimas nebaigtas:\n\n Mažiau nei prieš minutę Nustatomas laikas pompoje Nustatoma data pompoje - Nepavyko paleisti + Neinicializuota Ieškoma pompos Paruošta Sustabdyta @@ -70,7 +70,7 @@ Jei po daugiau nei ~5 minučių sujungimas nebaigtas:\n\n Įkeliama BPD istorija Atnaujinamas laikas ir data pompoje Gaunamas pompos statusas - PIN neteisingas. Patikrinkite, ar visi skaičiai teisingi. Jei problema kartojasi, atšaukite ir pakartokite susiejimą. + PIN netinka. Patikrinkite, ar visi skaičiai teisingi. Jei problema kartojasi, atšaukite ir pakartokite susiejimą. Aptikimo trukmė (sekundėmis) Įgalinti išsamius Combo įrašus Nuskaitoma bazė; %1$d elementų nuskaityta @@ -81,7 +81,7 @@ Jei po daugiau nei ~5 minučių sujungimas nebaigtas:\n\n Bolusas atšauktas Boluso suleisti nepavyko. Panašu, kad nė kiek insulino nesuleista. Patikrinkite pompos įstoriją, kad įsitikintumėte tuo, ir bandykite vėl. Bolusai nekartojami automatiškai, siekiant apsisaugoti nuo programos klaidų. Bolusas nesuleistas - Neįmanoma pasiekti pompos duomenų; pompa turi būti susieta iš naujo + Neįmanoma gauti pompos duomenų; pompa turi būti susieta iš naujo Aptikti neįskaičiuoti insulino suleidimai. Bolusas atšaukiamas saugumui užtikrinti. Neteisingas aktyvus bazės profilis; aktyvus turi būti profilis 1, ne profilis %1$d Neatpažintas Combo įspėjimas @@ -99,17 +99,17 @@ Jei po daugiau nei ~5 minučių sujungimas nebaigtas:\n\n Ignoruoti perteklines 100% bazės užklausas Nustatant laikiną bazę pasiektas nenumatytas limitas: LB tikslas %1$d%%, limitas %1$d%% Negalima nustatyti absoliučios laikinos bazės, jei pagrindinė bazė yra nulinė - Susieti AndroidAPS ir Android su dar nesusieta Accu-Chek Combo pompa - Atsieti AndroidAPS ir Android su dabartine susieta Accu-Chek Combo pompa + Susieti AndroidAPS su dar nesusieta Accu-Chek Combo pompa + Atsieti AndroidAPS ir Android nuo dabartinės susietos Accu-Chek Combo pompos Aptikta ir sustabdyta nežinoma laikina bazė: %1$d%%; likęs laikas: %2$s Sujungimo klaida: %1$s Pask. prisijung.: prieš %1$d min - Įspėjimas: %s + Perspėjimas: %s Pask.bolusas: %1$s @ %2$s Laik.bazė: %s Rezerv: %dvv - tuščias - žemas + tuščia + senka pilna Bat: %s Automatiškai aptikti ir registruoti insulino rezervuaro keitimą @@ -123,5 +123,5 @@ Jei po daugiau nei ~5 minučių sujungimas nebaigtas:\n\n Neįmanoma prisijungti prie pompos dėl pompos klaidos. Turite išspręsti klaidą ir palaukti 5 min, arba paspausti mygtuką Atnaujinti valdiklio skirtuke. Atnaujinamas pompos statusas po klaidos pranešimo Grįžti atgal - Neįmanoma prijungti pompos, nes nenustatytas valdiklis. Dažniausiai taip nutinka, kai nesuteiktas Bluetooth leidimas. Grįžkite atgal, leiskite Bluetooth ir bandykite vėl. + Neįmanoma susieti pompos, nes nenustatytas valdiklis. Dažniausiai taip nutinka, kai nesuteiktas Bluetooth leidimas. Grįžkite atgal, įgalinkite Bluetooth ir bandykite vėl. diff --git a/pump/dana/src/main/java/info/nightscout/pump/dana/activities/DanaHistoryActivity.kt b/pump/dana/src/main/java/info/nightscout/pump/dana/activities/DanaHistoryActivity.kt index 8b1f74ab7d..5a83da3269 100644 --- a/pump/dana/src/main/java/info/nightscout/pump/dana/activities/DanaHistoryActivity.kt +++ b/pump/dana/src/main/java/info/nightscout/pump/dana/activities/DanaHistoryActivity.kt @@ -8,7 +8,7 @@ import android.widget.AdapterView import android.widget.ArrayAdapter import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import dagger.android.support.DaggerAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.interfaces.Constants import info.nightscout.interfaces.plugin.ActivePlugin @@ -37,7 +37,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import javax.inject.Inject -class DanaHistoryActivity : DaggerAppCompatActivity() { +class DanaHistoryActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var rxBus: RxBus @Inject lateinit var rh: ResourceHelper diff --git a/pump/dana/src/main/java/info/nightscout/pump/dana/activities/DanaUserOptionsActivity.kt b/pump/dana/src/main/java/info/nightscout/pump/dana/activities/DanaUserOptionsActivity.kt index cf6be32b8b..66211cb83b 100644 --- a/pump/dana/src/main/java/info/nightscout/pump/dana/activities/DanaUserOptionsActivity.kt +++ b/pump/dana/src/main/java/info/nightscout/pump/dana/activities/DanaUserOptionsActivity.kt @@ -2,7 +2,7 @@ package info.nightscout.pump.dana.activities import android.content.Context import android.os.Bundle -import dagger.android.support.DaggerAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.interfaces.Constants import info.nightscout.interfaces.plugin.ActivePlugin @@ -26,7 +26,7 @@ import javax.inject.Inject import kotlin.math.max import kotlin.math.min -class DanaUserOptionsActivity : DaggerAppCompatActivity() { +class DanaUserOptionsActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var rxBus: RxBus @Inject lateinit var aapsLogger: AAPSLogger @@ -42,7 +42,7 @@ class DanaUserOptionsActivity : DaggerAppCompatActivity() { private val disposable = CompositeDisposable() // This is for Dana pumps only - private fun isRS() = activePlugin.activePump.pumpDescription.pumpType == PumpType.DANA_RS + private fun isRS() = activePlugin.activePump.pumpDescription.pumpType == PumpType.DANA_RS || activePlugin.activePump.pumpDescription.pumpType == PumpType.DANA_I private fun isDanaR() = activePlugin.activePump.pumpDescription.pumpType == PumpType.DANA_R private fun isDanaRv2() = activePlugin.activePump.pumpDescription.pumpType == PumpType.DANA_RV2 diff --git a/pump/dana/src/main/res/values-lt-rLT/strings.xml b/pump/dana/src/main/res/values-lt-rLT/strings.xml index d6114c8e86..822a48c2ec 100644 --- a/pump/dana/src/main/res/values-lt-rLT/strings.xml +++ b/pump/dana/src/main/res/values-lt-rLT/strings.xml @@ -1,9 +1,9 @@ - Sujungiama + Susiejimas Nerasta jokio prietaiso - Sujungta - Sujungimui skirtas laikas baigėsi + Susieta OK + Susiejimui skirtas laikas baigėsi Laukiama ryšio su pompa Dana-i/RS Dana @@ -15,7 +15,7 @@ Įvesta: %1$.2fvv Suleista: %2$.2fvv Klaidos kodas: %3$s Vertė nėra tinkamai nustatyta Nustatyti bazės žingsnį 0.01 vv/val - Panaikinti sąsajos informaciją? + Panaikinti susiejimo informaciją? %1$s\nModelis: %2$02X\nProtokolas: %3$02X\nKodas: %4$02X Apdorojama Aktyvuoti ištęstinius bolusus pompoje @@ -44,7 +44,7 @@ Gaunamas pompos laikas Didelis laiko neatitikimas Didelis laiko skirtumas:\nLaikas pompoje skiriasi daugiau nei 1,5 val.\nNustatykite laiką pompoje rankiniu būdu ir įsitikinkite, kad įrašai iš pompos istorijos nesukelia netikėtų sistemos veiksmų.\nJei įmanoma, ištrinkite istoriją pompoje prieš keisdami laiką arba atjunkite uždarąjį ciklą vienai IVT po paskutinio neteisingo istorijos įrašo ar mažiausiai vienai IVT nuo dabar. - Atnaujinkite pompos ir telefono ryšį! + Susiekite pompą su telefonu! Artėja paros insulino limitas Pradedamas bolusas Laukiama boluso pabaigos. Liko %1$d s. diff --git a/pump/danars/src/main/java/info/nightscout/pump/danars/activities/BLEScanActivity.kt b/pump/danars/src/main/java/info/nightscout/pump/danars/activities/BLEScanActivity.kt index a9dfbb5085..e4292a17f3 100644 --- a/pump/danars/src/main/java/info/nightscout/pump/danars/activities/BLEScanActivity.kt +++ b/pump/danars/src/main/java/info/nightscout/pump/danars/activities/BLEScanActivity.kt @@ -20,7 +20,7 @@ import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.TextView import androidx.core.app.ActivityCompat -import dagger.android.support.DaggerAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.ui.toast.ToastUtils import info.nightscout.core.utils.extensions.safeEnable import info.nightscout.interfaces.pump.BlePreCheck @@ -32,7 +32,7 @@ import info.nightscout.shared.sharedPreferences.SP import java.util.regex.Pattern import javax.inject.Inject -class BLEScanActivity : DaggerAppCompatActivity() { +class BLEScanActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var sp: SP @Inject lateinit var blePreCheck: BlePreCheck diff --git a/pump/danars/src/main/java/info/nightscout/pump/danars/activities/EnterPinActivity.kt b/pump/danars/src/main/java/info/nightscout/pump/danars/activities/EnterPinActivity.kt index 125cc74ef5..d642dc5f58 100644 --- a/pump/danars/src/main/java/info/nightscout/pump/danars/activities/EnterPinActivity.kt +++ b/pump/danars/src/main/java/info/nightscout/pump/danars/activities/EnterPinActivity.kt @@ -2,7 +2,7 @@ package info.nightscout.pump.danars.activities import android.os.Bundle import android.util.Base64 -import dagger.android.support.DaggerAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.ui.dialogs.OKDialog import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.core.utils.hexStringToByteArray @@ -21,7 +21,7 @@ import io.reactivex.rxjava3.kotlin.plusAssign import javax.inject.Inject import kotlin.experimental.xor -class EnterPinActivity : DaggerAppCompatActivity() { +class EnterPinActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var danaRSPlugin: DanaRSPlugin diff --git a/pump/danars/src/main/java/info/nightscout/pump/danars/activities/PairingHelperActivity.kt b/pump/danars/src/main/java/info/nightscout/pump/danars/activities/PairingHelperActivity.kt index c5bed29d63..f8fb960f45 100644 --- a/pump/danars/src/main/java/info/nightscout/pump/danars/activities/PairingHelperActivity.kt +++ b/pump/danars/src/main/java/info/nightscout/pump/danars/activities/PairingHelperActivity.kt @@ -4,10 +4,10 @@ import android.annotation.SuppressLint import android.content.Intent import android.content.pm.ActivityInfo import android.os.Bundle -import dagger.android.support.DaggerAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.pump.danars.dialogs.PairingProgressDialog -class PairingHelperActivity : DaggerAppCompatActivity() { +class PairingHelperActivity : TranslatedDaggerAppCompatActivity() { var dialog: PairingProgressDialog? = null diff --git a/pump/danars/src/main/java/info/nightscout/pump/danars/services/BLEComm.kt b/pump/danars/src/main/java/info/nightscout/pump/danars/services/BLEComm.kt index 78ef823b75..60af389f3f 100644 --- a/pump/danars/src/main/java/info/nightscout/pump/danars/services/BLEComm.kt +++ b/pump/danars/src/main/java/info/nightscout/pump/danars/services/BLEComm.kt @@ -838,6 +838,7 @@ class BLEComm @Inject internal constructor( if (!message.isReceived) { aapsLogger.warn(LTag.PUMPBTCOMM, "Reply not received " + message.friendlyName) message.handleMessageNotReceived() + disconnect("Reply not received") } // verify encryption for v3 & BLE if (message is DanaRSPacketEtcKeepConnection) diff --git a/pump/danars/src/main/java/info/nightscout/pump/danars/services/DanaRSService.kt b/pump/danars/src/main/java/info/nightscout/pump/danars/services/DanaRSService.kt index 88a5ea63df..1a63f6433c 100644 --- a/pump/danars/src/main/java/info/nightscout/pump/danars/services/DanaRSService.kt +++ b/pump/danars/src/main/java/info/nightscout/pump/danars/services/DanaRSService.kt @@ -371,6 +371,7 @@ class DanaRSService : DaggerService() { if (!isConnected) return false val status = DanaRSPacketGeneralInitialScreenInformation(injector) sendMessage(status) + if (status.failed) return false if (status.isTempBasalInProgress) { rxBus.send(EventPumpStatusChanged(rh.gs(info.nightscout.pump.dana.R.string.stoppingtempbasal))) sendMessage(DanaRSPacketBasalSetCancelTemporaryBasal(injector)) @@ -391,6 +392,7 @@ class DanaRSService : DaggerService() { fun highTempBasal(percent: Int): Boolean { val status = DanaRSPacketGeneralInitialScreenInformation(injector) sendMessage(status) + if (status.failed) return false if (status.isTempBasalInProgress) { rxBus.send(EventPumpStatusChanged(rh.gs(info.nightscout.pump.dana.R.string.stoppingtempbasal))) sendMessage(DanaRSPacketBasalSetCancelTemporaryBasal(injector)) @@ -414,6 +416,7 @@ class DanaRSService : DaggerService() { } val status = DanaRSPacketGeneralInitialScreenInformation(injector) sendMessage(status) + if (status.failed) return false if (status.isTempBasalInProgress) { rxBus.send(EventPumpStatusChanged(rh.gs(info.nightscout.pump.dana.R.string.stoppingtempbasal))) sendMessage(DanaRSPacketBasalSetCancelTemporaryBasal(injector)) diff --git a/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/activities/DiaconnG8BLEScanActivity.kt b/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/activities/DiaconnG8BLEScanActivity.kt index 0a38bcac2e..4e9b63f519 100644 --- a/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/activities/DiaconnG8BLEScanActivity.kt +++ b/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/activities/DiaconnG8BLEScanActivity.kt @@ -23,7 +23,7 @@ import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.TextView import androidx.core.app.ActivityCompat -import dagger.android.support.DaggerAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.ui.toast.ToastUtils import info.nightscout.core.utils.extensions.safeEnable import info.nightscout.interfaces.pump.BlePreCheck @@ -35,7 +35,7 @@ import info.nightscout.shared.sharedPreferences.SP import java.util.UUID import javax.inject.Inject -class DiaconnG8BLEScanActivity : DaggerAppCompatActivity() { +class DiaconnG8BLEScanActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var sp: SP @Inject lateinit var blePreCheck: BlePreCheck diff --git a/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/activities/DiaconnG8HistoryActivity.kt b/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/activities/DiaconnG8HistoryActivity.kt index a32f3d046f..0f65009a4c 100644 --- a/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/activities/DiaconnG8HistoryActivity.kt +++ b/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/activities/DiaconnG8HistoryActivity.kt @@ -9,7 +9,7 @@ import android.widget.ArrayAdapter import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import dagger.android.support.DaggerAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.interfaces.plugin.ActivePlugin import info.nightscout.interfaces.profile.ProfileFunction @@ -31,7 +31,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import javax.inject.Inject -class DiaconnG8HistoryActivity : DaggerAppCompatActivity() { +class DiaconnG8HistoryActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var fabricPrivacy: FabricPrivacy diff --git a/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/activities/DiaconnG8UserOptionsActivity.kt b/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/activities/DiaconnG8UserOptionsActivity.kt index c75ae8484b..89457c0e1d 100644 --- a/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/activities/DiaconnG8UserOptionsActivity.kt +++ b/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/activities/DiaconnG8UserOptionsActivity.kt @@ -5,7 +5,7 @@ import android.os.Bundle import android.view.View import android.widget.AdapterView import android.widget.ArrayAdapter -import dagger.android.support.DaggerAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.ui.toast.ToastUtils import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.interfaces.plugin.ActivePlugin @@ -23,7 +23,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import java.text.DecimalFormat import javax.inject.Inject -class DiaconnG8UserOptionsActivity : DaggerAppCompatActivity() { +class DiaconnG8UserOptionsActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var context: Context diff --git a/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/service/DiaconnG8Service.kt b/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/service/DiaconnG8Service.kt index d412ab138f..907b4c90c4 100644 --- a/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/service/DiaconnG8Service.kt +++ b/pump/diaconn/src/main/java/info/nightscout/pump/diaconn/service/DiaconnG8Service.kt @@ -505,7 +505,7 @@ class DiaconnG8Service : DaggerService() { val waitTime = (expectedEnd - System.currentTimeMillis()) / 1000 bolusingEvent.status = String.format(rh.gs(R.string.waitingforestimatedbolusend), if (waitTime < 0) 0 else waitTime) var progressPecent = 0 - if (totalwaitTime > waitTime) { + if (totalwaitTime > waitTime && totalwaitTime > 0) { progressPecent = ((totalwaitTime - waitTime) * 100 / totalwaitTime).toInt() } bolusingEvent.percent = min(progressPecent, 100) diff --git a/pump/diaconn/src/main/res/values-iw-rIL/strings.xml b/pump/diaconn/src/main/res/values-iw-rIL/strings.xml index c6ca4da148..f9c12ccdd4 100644 --- a/pump/diaconn/src/main/res/values-iw-rIL/strings.xml +++ b/pump/diaconn/src/main/res/values-iw-rIL/strings.xml @@ -97,6 +97,7 @@ איפוס לאחר כיבוי חירום איפוס לאחר החלפת סוללה איפוס לאחר כיול + איפוס הגדרה לאחר קדם-משלוח אתחול מערכת בלתי צפוי הושלם חסימת הזרקה @@ -119,13 +120,17 @@ רישום המזון נכשל הצלחה רישום המזון הצליח + לא ניתן להגדיר - שגיאת פרמטר קלט. לא ניתן להגדיר שגיאת פרוטוקול ספציפי. הארוחה נרשמה, לא ניתן להזריק. בוטל על ידי המשאבה + בביצוע פעולות אחרות - הגבלת הגדרות האפליקציה. בזמן הזרקת בולוס, הזרקות אחרות אינן אפשריות. + נדרש לחדש את הזרקת הבאזלי בוטל עקב חוסר תגובה מהמשאבה. ההזרקה לא אפשרית עקב סוללה חלשה. חסר אינסולין, לא ניתן להזריק. + לא ניתן להזריק כיוון שזה חורג מהמגבלה. ההזרקה לא אפשרית כי היא חורגת מהמינון היומי המותר. לאחר סיום תצורת הבזאלי, תותר הזרקתו. הפקודה לא נשלחה. בבקשה נסה שוב. @@ -135,6 +140,8 @@ בעת השהייה עקב סוכר נמוך, ההזרקה מוגבלת מצב השהייה עקב סוכר נמוך מופעל, פקודת ON נדחתה. מצב השהייה עקב סוכר נמוך כבוי, פקודת OFF נדחתה. + התחלת באזל זמני חדש נדחת כאשר באזלי זמני אחר כבר פעיל + הפסקת באזלי זמני נדחת כאשר לא פעיל באזלי זמני שלח יומני משאבה לענן Diaconn. סנכרון Diaconn Cloud diff --git a/pump/diaconn/src/main/res/values-lt-rLT/strings.xml b/pump/diaconn/src/main/res/values-lt-rLT/strings.xml index 53e61ee905..4cb6999c6e 100644 --- a/pump/diaconn/src/main/res/values-lt-rLT/strings.xml +++ b/pump/diaconn/src/main/res/values-lt-rLT/strings.xml @@ -12,8 +12,8 @@ Užpildyti sistemą Užpildymas Sustabdyta - Sujungta - Laukiama sujungimo + Susieta OK + Laukiama susiejimo Versija Netinkama susiejimo informacija. Bandoma susieti iš naujo Gaunami pompos nustatymai @@ -23,7 +23,7 @@ Artėja paros insulino limitas Pradedamas boluso leidimas Laukiama boluso suleidimo pabaigos - Gaunama boluso būklė + Gaunamas boluso statusas Stabdoma laikina bazė Laikinos bazės nustatymas Ištęsto boluso nustatymas @@ -44,13 +44,13 @@ Pasirinkta pompa Naudoti ištęstinį bolusą >200%% Rodyti ištęstinį boliusą kaip %% - Bluetooth būklė + Bluetooth statusas BPD Boluso žingsnis Bazės žingsnis Programinė įranga VARTOTOJO PARINKTYS - Atnaujinkite pompos ir telefono ryšį! + Susiekite pompą su telefonu! "Apdorojama " aps_last_log_num aps_wrapping_count @@ -148,8 +148,8 @@ Įtraukti į Pastabas įvykį „Infuzijos vamzdelio pakeitimas“, jei jis aptinkamas istorijoje Pradėta laikina bazė Negalima leisti insulino kol veikia sustabdymo prie žemos glikemijos režimas - Sustabdymo prie žemos glikemijos režimas aktyvus, komanda ĮJUNGTI atmesta. - Sustabdymo prie žemos glikemijos režimas išjungtas, komanda IŠJUNGTI atmesta. + Aktyvus stabdymo prie žemos glikemijos režimas, komanda ĮJUNGTI atmesta. + Išjungtas stabdymo prie žemos glikemijos režimas, komanda IŠJUNGTI atmesta. Laikinos bazės paleidimas atmestas, nes jau veikia nustatyta laikina bazė Laikinos bazės stabdymas atmestas, nes nėra nustatyta laikina bazė Siųsti pompos įrašus į Diaconn Cloud. diff --git a/pump/eopatch-core/libs/eopatch_core.aar b/pump/eopatch-core/libs/eopatch_core.aar index 7c48b147a2..9107760155 100644 Binary files a/pump/eopatch-core/libs/eopatch_core.aar and b/pump/eopatch-core/libs/eopatch_core.aar differ diff --git a/pump/eopatch/build.gradle b/pump/eopatch/build.gradle index af5c762b80..45311a8421 100644 --- a/pump/eopatch/build.gradle +++ b/pump/eopatch/build.gradle @@ -29,7 +29,7 @@ dependencies { implementation project(':core:utils') implementation project(':core:ui') - api 'com.google.guava:guava:31.1-jre' + api 'com.google.guava:guava:32.0.0-jre' //RxAndroidBle implementation "com.polidea.rxandroidble3:rxandroidble:1.17.2" diff --git a/pump/eopatch/src/main/AndroidManifest.xml b/pump/eopatch/src/main/AndroidManifest.xml index b84a88757c..9a804daffe 100644 --- a/pump/eopatch/src/main/AndroidManifest.xml +++ b/pump/eopatch/src/main/AndroidManifest.xml @@ -2,9 +2,15 @@ - - - + + + diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EopatchPumpPlugin.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EopatchPumpPlugin.kt index 1d22fbe39d..c325d4a2c1 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EopatchPumpPlugin.kt +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EopatchPumpPlugin.kt @@ -107,14 +107,18 @@ class EopatchPumpPlugin @Inject constructor( alarmManager.init() }) { throwable: Throwable -> fabricPrivacy.logException(throwable) } ) + } + // following was moved from specialEnableCondition() // specialEnableCondition() is called too often to add there executive code - + // This is a required code for maintaining the patch activation phase and maintaining the alarm. It should not be deleted. + override fun specialEnableCondition(): Boolean { //BG -> FG, restart patch activation and trigger unhandled alarm if (preferenceManager.isInitDone()) { patchManager.checkActivationProcess() alarmManager.restartAll() } + return super.specialEnableCondition() } override fun onStop() { diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/OsAlarmService.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/OsAlarmService.java deleted file mode 100644 index a03ea1520b..0000000000 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/OsAlarmService.java +++ /dev/null @@ -1,77 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.eopatch; - -import android.app.NotificationManager; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.IBinder; - -import java.util.Objects; - -import io.reactivex.rxjava3.disposables.CompositeDisposable; - -public class OsAlarmService extends Service { - - public static final int FOREGROUND_NOTIFICATION_ID = 34534554; - - private CompositeDisposable compositeDisposable; - - private boolean foreground = false; - - @Override - public void onCreate() { - super.onCreate(); - - compositeDisposable = new CompositeDisposable(); - - startForeground(); - - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - startForeground(); - - String action = null; - - if (action == null) { - return Service.START_NOT_STICKY; - } - - return START_STICKY; - } - - @Override - public void onDestroy() { - super.onDestroy(); - ((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).cancel(FOREGROUND_NOTIFICATION_ID); - - compositeDisposable.dispose(); - } - - public synchronized void startForeground() { - if (!foreground) { - foreground = true; - } - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - public static void start(Context context) { - Intent intent = new Intent(context, OsAlarmService.class); - -// context.startForegroundService(intent); - } - - public static void notifyNotification(Context context, boolean isNetworkAvailable) { - notifyNotification(context); - } - - public static void notifyNotification(Context context) { -// Notification builder = getNotification(context); -// ((NotificationManager) Objects.requireNonNull(context.getSystemService(Context.NOTIFICATION_SERVICE))).notify(FOREGROUND_NOTIFICATION_ID, builder); - } -} \ No newline at end of file diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmManager.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmManager.kt index 39d0776648..f06a256a75 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmManager.kt +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmManager.kt @@ -14,6 +14,7 @@ import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPatchManager import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager import info.nightscout.androidaps.plugins.pump.eopatch.code.AlarmCategory import info.nightscout.androidaps.plugins.pump.eopatch.event.EventEoPatchAlarm +import info.nightscout.androidaps.plugins.pump.eopatch.extension.takeOne import info.nightscout.androidaps.plugins.pump.eopatch.ui.AlarmHelperActivity import info.nightscout.androidaps.plugins.pump.eopatch.vo.Alarms import info.nightscout.core.utils.fabric.FabricPrivacy @@ -160,10 +161,11 @@ class AlarmManager @Inject constructor() : IAlarmManager { id = Notification.EOELOW_PATCH_ALERTS + (alarmCode.aeCode + 10000), text = alarmMsg, level = Notification.URGENT, - buttonText = R.string.confirm, + buttonText = (alarmCode == B001).takeOne(R.string.string_resume, R.string.confirm), action = { compositeDisposable.add( Single.just(isValid(alarmCode)) + .observeOn(aapsSchedulers.main) //don't remove .flatMap { isValid -> return@flatMap if (isValid) mAlarmProcess.doAction(context, alarmCode) else Single.just(IAlarmProcess.ALARM_HANDLED) @@ -179,18 +181,17 @@ class AlarmManager @Inject constructor() : IAlarmManager { ) } updateState(alarmCode, AlarmState.HANDLE) + }else if(ret == IAlarmProcess.ALARM_HANDLED_BUT_NEED_STOP_BEEP){ + pm.getAlarms().needToStopBeep.add(alarmCode) + updateState(alarmCode, AlarmState.HANDLE) } else { - uiInteraction.addNotification( - id = Notification.EOELOW_PATCH_ALERTS + (alarmCode.aeCode + 10000), - text = alarmMsg, - level = Notification.URGENT - ) + showNotification(alarmCode) } } ) }, soundId = info.nightscout.core.ui.R.raw.error, - date = pm.getPatchConfig().patchWakeupTimestamp + TimeUnit.SECONDS.toMillis(timeOffset) + date = pm.getAlarms().getOccuredAlarmTimestamp(alarmCode) ) } diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmProcess.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmProcess.kt index 1a80d235a7..bf596bf026 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmProcess.kt +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmProcess.kt @@ -35,6 +35,7 @@ import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.B012 import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.B018 import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPatchManager import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse +import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TemperatureResponse import info.nightscout.androidaps.plugins.pump.eopatch.event.EventDialog import info.nightscout.androidaps.plugins.pump.eopatch.event.EventProgressDialog @@ -55,6 +56,7 @@ interface IAlarmProcess { const val ALARM_UNHANDLED = 0 const val ALARM_PAUSE = 1 const val ALARM_HANDLED = 2 + const val ALARM_HANDLED_BUT_NEED_STOP_BEEP = 3 } } @@ -68,14 +70,14 @@ class AlarmProcess(val patchManager: IPatchManager, val rxBus: RxBus) : IAlarmPr A117, A118 -> patchDeactivationAction(context) A007 -> inappropriateTemperatureAction(context) A016 -> needleInsertionErrorAction(context) - B000, B003, B018 -> Single.just(IAlarmProcess.ALARM_HANDLED) - B005, B006 -> Single.just(IAlarmProcess.ALARM_HANDLED) + B000 -> Single.just(IAlarmProcess.ALARM_HANDLED) + B003, B005, B006, B018 -> stopAeBeepAction(context, code) B012 -> Single.just(IAlarmProcess.ALARM_HANDLED) } } private fun startActivityWithSingleTop(context: Context, intent: Intent) { - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) context.startActivity(intent) } @@ -104,7 +106,7 @@ class AlarmProcess(val patchManager: IPatchManager, val rxBus: RxBus) : IAlarmPr Single.fromCallable { showCommunicationFailedDialog { startActivityWithSingleTop(context, - createIntentForCheckConnection(context, goHomeAfterDiscard = true, forceDiscard = true)) + createIntentForCheckConnection(context, goHomeAfterDiscard = true, forceDiscard = true, isAlarmHandling = true)) } IAlarmProcess.ALARM_PAUSE } @@ -115,6 +117,7 @@ class AlarmProcess(val patchManager: IPatchManager, val rxBus: RxBus) : IAlarmPr return actionWithPatchCheckConnection(context) { patchManager.resumeBasal() .map { obj: BaseResponse -> obj.isSuccess } + .onErrorReturn { false } .flatMap { Single.just(it.takeOne(IAlarmProcess.ALARM_HANDLED, IAlarmProcess.ALARM_UNHANDLED)) } } } @@ -148,4 +151,15 @@ class AlarmProcess(val patchManager: IPatchManager, val rxBus: RxBus) : IAlarmPr .defaultIfEmpty(IAlarmProcess.ALARM_UNHANDLED) } } + + private fun stopAeBeepAction(context: Context, alarm: AlarmCode): Single { + if (patchManager.patchConnectionState.isConnected) { + return patchManager.stopAeBeep(alarm.aeCode) + .map { obj: PatchBooleanResponse -> obj.isSuccess } + .onErrorReturn { false } + .flatMap { Single.just(it.takeOne(IAlarmProcess.ALARM_HANDLED, IAlarmProcess.ALARM_HANDLED_BUT_NEED_STOP_BEEP)) } + }else{ + return Single.just(IAlarmProcess.ALARM_HANDLED_BUT_NEED_STOP_BEEP) + } + } } \ No newline at end of file diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmRegistry.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmRegistry.kt index 4ba6f195ac..ae020d1ccf 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmRegistry.kt +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmRegistry.kt @@ -17,6 +17,7 @@ import info.nightscout.rx.AapsSchedulers import info.nightscout.rx.bus.RxBus import info.nightscout.rx.events.EventDismissNotification import info.nightscout.rx.logging.AAPSLogger +import info.nightscout.shared.utils.DateUtil import io.reactivex.rxjava3.core.Maybe import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -120,6 +121,7 @@ class AlarmRegistry @Inject constructor() : IAlarmRegistry { return Maybe.fromCallable { cancelOsAlarmInternal(alarmCode) val pendingIntent = createPendingIntent(alarmCode, 0) + aapsLogger.debug("[${alarmCode}] OS Alarm added. ${DateUtil(mContext).toISOString(triggerTime)}") mOsAlarmManager.setAlarmClock(AlarmClockInfo(triggerTime, pendingIntent), pendingIntent) alarmCode } diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManager.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManager.java index 16f53a55dc..9330d2392b 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManager.java +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManager.java @@ -360,7 +360,7 @@ public class PatchManager implements IPatchManager { if (detailedBolusInfo.insulin > 0) { pumpSync.syncBolusWithPumpId( - detailedBolusInfo.timestamp, + dateUtil.now(), // Use real timestamp to have it different from carbs (otherwise NS sync fail) detailedBolusInfo.insulin, detailedBolusInfo.getBolusType(), dateUtil.now(), @@ -368,15 +368,6 @@ public class PatchManager implements IPatchManager { patchManager.pm.getPatchSerial() ); } - if (detailedBolusInfo.carbs > 0) { - pumpSync.syncCarbsWithTimestamp( - detailedBolusInfo.getCarbsTimestamp() != null ? detailedBolusInfo.getCarbsTimestamp() : detailedBolusInfo.timestamp, - detailedBolusInfo.carbs, - null, - PumpType.USER, - patchManager.pm.getPatchSerial() - ); - } } @Override diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManagerImpl.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManagerImpl.java index 834e131927..7e1868506e 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManagerImpl.java +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManagerImpl.java @@ -192,7 +192,6 @@ public class PatchManagerImpl { .filter(it -> patch.getConnectionState().isConnected()) .concatMapIterable(EventEoPatchAlarm::getAlarmCodes) .filter(AlarmCode::isPatchOccurrenceAlert) - .flatMap(it -> stopAeBeep(it.getAeCode()).toObservable()) .subscribe() ); @@ -250,6 +249,16 @@ public class PatchManagerImpl { } return Single.just(true); }) + .flatMap(ret -> { + if(!pm.getAlarms().getNeedToStopBeep().isEmpty()) { + return Observable.fromStream(pm.getAlarms().getNeedToStopBeep().stream()) + .flatMapSingle(alarmCode -> stopAeBeep(alarmCode.getAeCode()).doOnSuccess(patchBooleanResponse -> { + pm.getAlarms().getNeedToStopBeep().remove(alarmCode); + })) + .lastOrError(); + } + return Single.just(true); + }) .subscribe()); } diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartBondTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartBondTask.java index 1e9dcbf241..2be183aaf6 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartBondTask.java +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartBondTask.java @@ -36,11 +36,10 @@ public class StartBondTask extends TaskBase { }) .filter(result -> result == BluetoothDevice.BOND_BONDED) .map(result -> true) - .timeout(60, TimeUnit.SECONDS) + .timeout(35, TimeUnit.SECONDS) .doOnNext(v -> prefSetMacAddress(mac)) .doOnError(e -> { prefSetMacAddress(""); - aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StartBondTask error"); }) .firstOrError(); } diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopBasalTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopBasalTask.java index b10506ae19..b7ed078622 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopBasalTask.java +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopBasalTask.java @@ -30,6 +30,8 @@ public class StopBasalTask extends TaskBase { @Inject PumpSync pumpSync; @Inject UserEntryLogger uel; + @Inject UpdateConnectionTask updateConnectionTask; + private final BasalStop BASAL_STOP; private final BehaviorSubject bolusCheckSubject = BehaviorSubject.create(); private final BehaviorSubject exbolusCheckSubject = BehaviorSubject.create(); @@ -93,15 +95,11 @@ public class StopBasalTask extends TaskBase { .flatMap(v -> isReady()) .concatMapSingle(v -> BASAL_STOP.stop()) .doOnNext(this::checkResponse) + .doOnNext(v -> updateConnectionTask.enqueue()) .firstOrError() - .doOnSuccess(this::onBasalStopped) .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StopBasalTask error")); } - private void onBasalStopped(BasalStopResponse response) { - enqueue(TaskFunc.UPDATE_CONNECTION); - } - public synchronized void enqueue() { boolean ready = (disposable == null || disposable.isDisposed()); diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/AlarmHelperActivity.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/AlarmHelperActivity.kt index fb45db03bd..f459b3e28a 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/AlarmHelperActivity.kt +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/AlarmHelperActivity.kt @@ -8,14 +8,14 @@ import info.nightscout.androidaps.plugins.pump.eopatch.event.EventProgressDialog import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.AlarmDialog import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.ProgressDialogHelper import info.nightscout.core.ui.R -import info.nightscout.core.ui.activities.DialogAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.rx.AapsSchedulers import info.nightscout.rx.bus.RxBus import info.nightscout.shared.sharedPreferences.SP import io.reactivex.rxjava3.disposables.CompositeDisposable import javax.inject.Inject -class AlarmHelperActivity : DialogAppCompatActivity() { +class AlarmHelperActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var sp : SP @Inject lateinit var rxBus: RxBus @Inject lateinit var aapsSchedulers: AapsSchedulers diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/DialogHelperActivity.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/DialogHelperActivity.kt index 59e86714e2..ad8b16b73d 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/DialogHelperActivity.kt +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/DialogHelperActivity.kt @@ -3,9 +3,9 @@ package info.nightscout.androidaps.plugins.pump.eopatch.ui import android.os.Bundle import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.ActivationNotCompleteDialog import info.nightscout.core.ui.R -import info.nightscout.core.ui.activities.DialogAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity -class DialogHelperActivity : DialogAppCompatActivity() { +class DialogHelperActivity : TranslatedDaggerAppCompatActivity() { @Override override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EoBaseActivity.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EoBaseActivity.kt index fda4c8a6a6..8602ef800e 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EoBaseActivity.kt +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EoBaseActivity.kt @@ -7,14 +7,15 @@ import androidx.annotation.LayoutRes import androidx.databinding.DataBindingUtil import androidx.databinding.ViewDataBinding import androidx.lifecycle.ViewModelProvider -import dagger.android.support.DaggerAppCompatActivity import info.nightscout.androidaps.plugins.pump.eopatch.dagger.EopatchPluginQualifier import info.nightscout.core.ui.R +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.rx.AapsSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable import javax.inject.Inject -abstract class EoBaseActivity : DaggerAppCompatActivity(), EoBaseNavigator { +abstract class EoBaseActivity : TranslatedDaggerAppCompatActivity(), EoBaseNavigator { + @Inject @EopatchPluginQualifier lateinit var viewModelFactory: ViewModelProvider.Factory @@ -48,7 +49,7 @@ abstract class EoBaseActivity : DaggerAppCompatActivity(), } override fun finish(finishAffinity: Boolean) { - if(finishAffinity) { + if (finishAffinity) { finishAffinity() } else { finish() diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchActivity.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchActivity.kt index ec87c01790..1f9cc7f15d 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchActivity.kt +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchActivity.kt @@ -3,8 +3,7 @@ package info.nightscout.androidaps.plugins.pump.eopatch.ui import android.app.Dialog import android.content.Context import android.content.Intent -import android.media.MediaPlayer -import android.media.RingtoneManager +import android.content.pm.ActivityInfo import android.os.Bundle import android.view.MotionEvent import androidx.appcompat.app.AlertDialog @@ -21,8 +20,6 @@ import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewM import info.nightscout.core.utils.extensions.safeGetSerializableExtra class EopatchActivity : EoBaseActivity() { - - private var mediaPlayer: MediaPlayer? = null private var mPatchCommCheckDialog: Dialog? = null private var mProgressDialog: AlertDialog? = null @@ -38,9 +35,10 @@ class EopatchActivity : EoBaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED binding.apply { - viewModel = ViewModelProvider(this@EopatchActivity, viewModelFactory).get(EopatchViewModel::class.java) + viewModel = ViewModelProvider(this@EopatchActivity, viewModelFactory)[EopatchViewModel::class.java] viewModel?.apply { processIntent(intent) @@ -79,26 +77,26 @@ class EopatchActivity : EoBaseActivity() { setResult(RESULT_DISCARDED) if (intent.getBooleanExtra(EXTRA_GO_HOME, true)) { - backToHome(false) + backToHome() } else { this@EopatchActivity.finish() } }) } - PatchStep.COMPLETE -> backToHome(true) + PatchStep.COMPLETE -> backToHome() PatchStep.FINISH -> { if (!intent.getBooleanExtra(EXTRA_START_FROM_MENU, false) || intent.getBooleanExtra(EXTRA_GO_HOME, true) ) { - backToHome(false) + backToHome() } else { this@EopatchActivity.finish() } } - PatchStep.BACK_TO_HOME -> backToHome(false) + PatchStep.BACK_TO_HOME -> backToHome() PatchStep.CANCEL -> this@EopatchActivity.finish() else -> Unit } @@ -120,6 +118,7 @@ class EopatchActivity : EoBaseActivity() { val step = intent.safeGetSerializableExtra(EXTRA_START_PATCH_STEP, PatchStep::class.java) forceDiscard = intent.getBooleanExtra(EXTRA_FORCE_DISCARD, false) + isInAlarmHandling = intent.getBooleanExtra(EXTRA_IS_ALARM_HANDLING, false) if (intent.getBooleanExtra(EXTRA_START_WITH_COMM_CHECK, false)) { checkCommunication({ initializePatchStep(step) @@ -218,6 +217,7 @@ class EopatchActivity : EoBaseActivity() { } // EventType.SHOW_BONDED_DIALOG -> this@EopatchActivity.finish() EventType.SHOW_DISCARD_DIALOG -> { + val cancelLabel = isInAlarmHandling.takeOne(null, getString(R.string.cancel)) info.nightscout.core.ui.dialogs.AlertDialogHelper.Builder(this@EopatchActivity).apply { setTitle(R.string.string_discard_patch) if (isBolusActive) { @@ -236,7 +236,7 @@ class EopatchActivity : EoBaseActivity() { } } } - setNegativeButton(R.string.cancel) { _, _ -> + setNegativeButton(cancelLabel) { _, _ -> dismissProgressDialog() updateIncompletePatchActivationReminder() } @@ -254,7 +254,7 @@ class EopatchActivity : EoBaseActivity() { mProgressDialog?.let { try { mProgressDialog?.dismiss() - } catch (e: IllegalStateException) { + } catch (ignored: IllegalStateException) { } mProgressDialog = null } @@ -264,35 +264,16 @@ class EopatchActivity : EoBaseActivity() { mPatchCommCheckDialog?.let { try { mPatchCommCheckDialog?.dismiss() - } catch (e: IllegalStateException) { + } catch (ignored: IllegalStateException) { } mPatchCommCheckDialog = null } } - private fun backToHome(isActivated: Boolean) { - if (isActivated) { - mediaPlayer = MediaPlayer.create(this, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))?.apply { - setOnCompletionListener { - this@EopatchActivity.finish() - } - start() - } - } - + private fun backToHome() { this@EopatchActivity.finish() } - override fun onDestroy() { - super.onDestroy() - - mediaPlayer?.let { - it.stop() - it.release() - mediaPlayer = null - } - } - override fun onBackPressed() { binding.viewModel?.apply{ when(patchStep.value){ @@ -309,16 +290,18 @@ class EopatchActivity : EoBaseActivity() { const val EXTRA_START_WITH_COMM_CHECK = "EXTRA_START_WITH_COMM_CHECK" const val EXTRA_GO_HOME = "EXTRA_GO_HOME" const val EXTRA_FORCE_DISCARD = "EXTRA_FORCE_DISCARD" + const val EXTRA_IS_ALARM_HANDLING = "EXTRA_IS_ALARM_HANDLING" const val NORMAL_TEMPERATURE_MIN = 4 const val NORMAL_TEMPERATURE_MAX = 45 @JvmStatic @JvmOverloads - fun createIntentForCheckConnection(context: Context, goHomeAfterDiscard: Boolean = true, forceDiscard: Boolean = false): Intent { + fun createIntentForCheckConnection(context: Context, goHomeAfterDiscard: Boolean = true, forceDiscard: Boolean = false, isAlarmHandling: Boolean = false): Intent { return Intent(context, EopatchActivity::class.java).apply { putExtra(EXTRA_START_PATCH_STEP, PatchStep.CHECK_CONNECTION) putExtra(EXTRA_GO_HOME, goHomeAfterDiscard) putExtra(EXTRA_FORCE_DISCARD, forceDiscard) + putExtra(EXTRA_IS_ALARM_HANDLING, isAlarmHandling) } } diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/AlarmDialog.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/AlarmDialog.kt index 478101d5e0..d02bd53812 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/AlarmDialog.kt +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/AlarmDialog.kt @@ -14,6 +14,7 @@ import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmProcess import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmProcess import info.nightscout.androidaps.plugins.pump.eopatch.bindingadapters.setOnSafeClickListener import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPatchManager +import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager import info.nightscout.androidaps.plugins.pump.eopatch.databinding.DialogAlarmBinding import info.nightscout.androidaps.plugins.pump.eopatch.ui.AlarmHelperActivity import info.nightscout.core.ui.R @@ -32,6 +33,7 @@ class AlarmDialog : DaggerDialogFragment() { @Inject lateinit var patchManager: IPatchManager @Inject lateinit var rxBus: RxBus @Inject lateinit var aapsSchedulers: AapsSchedulers + @Inject lateinit var pm: IPreferenceManager var helperActivity: AlarmHelperActivity? = null var alarmCode: AlarmCode? = null @@ -84,7 +86,10 @@ class AlarmDialog : DaggerDialogFragment() { .subscribeOn(aapsSchedulers.io) .subscribe ({ ret -> aapsLogger.debug("Alarm processing result :${ret}") - if (ret == IAlarmProcess.ALARM_HANDLED) { + if (ret == IAlarmProcess.ALARM_HANDLED || ret == IAlarmProcess.ALARM_HANDLED_BUT_NEED_STOP_BEEP) { + if(ret == IAlarmProcess.ALARM_HANDLED_BUT_NEED_STOP_BEEP){ + pm.getAlarms().needToStopBeep.add(ac) + } alarmCode?.let{ patchManager.preferenceManager.getAlarms().handle(it) patchManager.preferenceManager.flushAlarms() diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/EopatchViewModel.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/EopatchViewModel.kt index 142d4fb646..392f4f9242 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/EopatchViewModel.kt +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/EopatchViewModel.kt @@ -74,6 +74,7 @@ class EopatchViewModel @Inject constructor( private const val MAX_ELAPSED_MILLIS_AFTER_EXPIRATION = -12L * 60 * 60 * 1000 } var forceDiscard = false + var isInAlarmHandling = false var connectionTryCnt = 0 val patchConfig: PatchConfig = patchManager.patchConfig @@ -312,6 +313,7 @@ class EopatchViewModel @Inject constructor( CommonUtils.dispose(mCommCheckDisposable) updateIncompletePatchActivationReminder() dismissPatchCommCheckDialogInternal(false) + connectionTryCnt = 0 } @Synchronized diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/Alarms.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/Alarms.kt index 58facc5ef2..95670262c1 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/Alarms.kt +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/Alarms.kt @@ -7,6 +7,7 @@ import info.nightscout.shared.sharedPreferences.SP import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.subjects.BehaviorSubject import java.util.* +import kotlin.collections.HashSet class Alarms: IPreference { @Transient @@ -26,6 +27,8 @@ class Alarms: IPreference { var occurred = HashMap() + var needToStopBeep = HashSet() + init { initObject() } @@ -36,6 +39,7 @@ class Alarms: IPreference { fun clear(){ registered.clear() occurred.clear() + needToStopBeep.clear() } fun update(other: Alarms) { @@ -75,6 +79,13 @@ class Alarms: IPreference { occurred.remove(alarmCode) } + fun getOccuredAlarmTimestamp(alarmCode: AlarmCode): Long{ + return if(occurred.containsKey(alarmCode)) + occurred.getValue(alarmCode).triggerTimeMilli + else + System.currentTimeMillis() + } + private fun isRegistered(alarmCode: AlarmCode): Boolean{ return registered.containsKey(alarmCode) } diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/PatchConfig.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/PatchConfig.kt index caa522392e..6a38a1aa6c 100644 --- a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/PatchConfig.kt +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/PatchConfig.kt @@ -144,8 +144,6 @@ class PatchConfig: IPreference { this.standardBolusInjectCount = 0 this.extendedBolusInjectCount = 0 this.basalInjectCount = 0 - this.lowReservoirAlertAmount = 10 - this.patchExpireAlertTime = 4 this.remainedInsulin = 0f } diff --git a/pump/eopatch/src/main/res/values-es-rES/strings.xml b/pump/eopatch/src/main/res/values-es-rES/strings.xml index 3626519488..53d7003bea 100644 --- a/pump/eopatch/src/main/res/values-es-rES/strings.xml +++ b/pump/eopatch/src/main/res/values-es-rES/strings.xml @@ -6,12 +6,12 @@ Alertas Alertas de reservorio bajo Recordatorio de expiración del parche - Recordatorios de zumbidos del parche + Recordatorios de pitidos del parche h:mm a Estado del Bluetooth Número de serie Número de lote - Fecha y hora para despertar & + Fecha de activación Fecha de expiración Estado Tasa basal base diff --git a/pump/eopatch/src/main/res/values-it-rIT/strings.xml b/pump/eopatch/src/main/res/values-it-rIT/strings.xml index 83ff84aad1..599168eeec 100644 --- a/pump/eopatch/src/main/res/values-it-rIT/strings.xml +++ b/pump/eopatch/src/main/res/values-it-rIT/strings.xml @@ -92,6 +92,9 @@ Annulla associazione giorni  U + Al momento della sospensione, il bolo & la basale temporanea attualmente in erogazione verranno cancellati. \n\nVelocità : %1$s U/hr\nTempo rimanente : %2$s\n& Insulin to go : %3$.2f U + Al momento della sospensione, il bolo attualmente in erogazione verrà cancellato. \n\nInsulin to go : %1$.2f U + Al momento della sospensione, la basale temporanea attualmente in erogazione verrà cancellata. \n\nVelocità : %1$s U/hr\nTempo rimanente : %2$s L\'erogazione di insulina sarà sospesa. Scarta/Cambia patch Attiva patch @@ -111,11 +114,13 @@ Riprova [Caution] NON rimuovere la protezione dell\'ago e il supporto adesivo sino a ulteriori istruzioni. Sei sicuro di scartare la patch corrent? + A causa di informazioni non confermate riguardo il bolo, il calcolo del bolo non può essere usato fino a %s.\n\nSei sicuro di voler scartare la patch corrente? Errore inserimento ago. Controlla che la direzione del foro rimanente dopo aver girato la leva sia dritta, quindi tocca \'Riprova\'. Tocca \'Scarta\' per disattivare la patch. Tempo sospensione basale Riprendi erogazione insulina + Fine sospensione insulina.\nTocca \'Conferma\' per riprendere l\'erogazione di insulina.\n\n[Caution]\nL\'erogazione di insulina non riprenderà fino a quando non tocchi \'Conferma\'.\nSe non tocchi \'Conferma\', potresti sviluppare iperglicemia. L\'erogazione di insulina è sospesa. L\'erogazione di insulina è ripresa. Fine diff --git a/pump/eopatch/src/main/res/values-iw-rIL/strings.xml b/pump/eopatch/src/main/res/values-iw-rIL/strings.xml index f2144dcf2d..2c5100ca00 100644 --- a/pump/eopatch/src/main/res/values-iw-rIL/strings.xml +++ b/pump/eopatch/src/main/res/values-iw-rIL/strings.xml @@ -1,21 +1,87 @@ + EOPatch2 + EOP2 + אינטגרציית משאבה עבור EOPatch2 / GlucoMen Day התראות + התראות על כמות אינסולין נמוכה + תזכורת תפוגת ה-Patch + התראות מצפצפות של ה-Patch + h:mm a + סטטוס BLE + מספר סידורי + מספר Lot + תאריך השכמה & זמן + מועד תפוגה סטטוס ערך בזאלי בסיסי מינון בזאלי זמני סה\"כ שהוזרק + הבאזלי של הפרופיל קטן מ-0.05 U/hr. ל-EOPatch יש יחידת הזרקה מינימלית של 0.05U. נא לנסות שוב לאחר הגדרת פרופיל ליותר מיחידת ההזרקה המינימלית. + לא נבחר פרופיל. נא לבחור פרופיל ולנסות שוב. יום + - 30 דקות שעה + שעה וחצי + שעתיים \u0020 + כדי להחליף ל Patch חדש, נדרש לנתק את ה-Patch הנוכחי. כל אספקת האינסולין מ-Patch תבוטל. + ניתוק ה-Patch הושלם בהצלחה. + ה-Patch הושבת.\nהיפטר מה-Patch.\nהסר את ה-Patch המחובר לגוף שלך. + בולוס בוצע כעת. האם אתה באמת רוצה לבטל את מתן האינסולין ולנתק את ה-Patch? + באזל זמני בוצע כעת. האם אתה באמת רוצה לבטל את מתן האינסולין ולנתק את ה-Patch? + בולוס ובאזל זמני בוצע כעת. האם אתה באמת רוצה לבטל את מתן האינסולין ולנתק את ה-Patch? + האם אתם בטוחים שברצונכם לנתק את ה-Patch? + אינסולין נותר + זמן נותר סיום + ניתוק + ניתוק Patch - שגיאת תקשורת במהלך ניתוק + כיבוי ידני של התראות ה-Patch + אם מבוצעת השבתה באופן חד צדדי במהלך שגיאת תקשורת, ייתכן שצפצוף האזעקה מה- Patch לא ייפסק. + במקרה זה, ניתן לעצור את זה לחלוטין על ידי לחיצה על לחצן הפסקת ההתראה הידני ב-Patch כמו שמתואר להלן: + מילוי אינסולין + 1/6 1. + נא למלא את ה-Patch באינסולין בטמפרטורת החדר. שימו לב לזווית המזרק. 2. + וודאו ששמעתם צפצוף אחד. + התחל צימוד + צימוד Patch + 2/6 + ה-Patch החדש מבצע צימוד.\nשמרו על המרחק קרוב ככל האפשר בין ה-Patch למכשיר החכם + מתכונן לחיבור ה-Patch + 3/6 + הורידו את כיסוי הדבק ולאחר מכן הקישו \'הבא\'. + [Caution1] אם המחט יצאה החוצה, הקישו על \'ניתוק\'. + [Caution2] אם ה-Patch רטוב או מלוכלך, או שהדבק שלו מקופל, הקישו על \'ניתוק\'. + מצמיד את ה-Patch + 4/6 + 5/6 + זה לוקח כ-30 שניות. + [Caution] לבטיחותכם, על תסירו את מנוף החדרת המחט עד שבדיקת הבטיחות הושלמה! + מחדיר את המחט + 6/6 + סובבו את מנוף החדרת המחט ליותר מ-100° על מנת להחדיר את המחט ולאחר מכן סובבו עד הסוף בכדי להסיר את המנוף + חכו לצפצוף יחיד ולאחר מכן הקישו \'הבא\'. + הביאו את המכשיר החכם קרוב יותר ל-Patch. + נא עברו למקום אחר ונסו שנית. + שגיאת תקשורת + תקשרות נתונים עם ה-Patch בוצעה בהצלחה + מחובר ל-Patch. + ביטול צימוד ימים +  יח\' + עם ההשעיה, הבולוס המסופק כעת & הבאזלי הזמני יבוטל. \n\nקצב: %1$s יח\'/ש\' +\nזמן שנותר: %2$s\n& אינסולין שנותר: %3$.2f יח\' + ניתוק/החלפת Patch + הפעלת Patch + ניתוק Patch חידוש + ניתוק + בדיקת תקשורת השהיה מושהה פועל @@ -26,9 +92,22 @@ משנה מעבד נסה שוב + [Caution] אל תסירו את מכסה המחט ואת כיסוי הדבק עד להוראה נוספת. + האם אתם בטוחים רוצים לנתק את ה-Patch הנוכחי? + עקב מידע אינו וודאי בנוגע לבולוס, לא ניתן להשתמש ב-Bolus calc עד %s.\n\nהאם אתם בטוחים רוצים לנתק את ה-Patch הנוכחי? + שגיאה בהכנסת מחט. + בדקו שכיוון חור פתח המחט ישר, לאחר סיבוב מנוף החרדת המחט, ולאחר מכן לחצו \'נסה שוב\'. + לחצו על \'ניתוק\' בכדי לנתק את ה-Patch. + זמן השעיית הבאזלי + חידוש הזרקת האינסולין + סוף הפסקת האינסולין.\nלחצו על \'אשר\' כדי לחדש את מתן האינסולין.\n\n[Caution]\nמתן האינסולין לא יחודש עד שתלחצו על \'אשר\'.\nאם לא תלחצו על \'אשר\', הסוכר עלול לעלות ותגיעו לכדי היפרגליקמיה. הזרקת האינסולין מופסקת. + הזרקת האינסולין התחדשה. סיום זמן נותר: %1$s:%2$s הבא נסה שוב אחרי ניסיון תקשורת לפאץ\'. + ההשעיה נכשלה. + חידוש ההזרקה נכשל. + EEE, d MMM, yyyy hh:mm a diff --git a/pump/eopatch/src/main/res/values-ko-rKR/strings.xml b/pump/eopatch/src/main/res/values-ko-rKR/strings.xml index ff44bc370c..e3b89c5813 100644 --- a/pump/eopatch/src/main/res/values-ko-rKR/strings.xml +++ b/pump/eopatch/src/main/res/values-ko-rKR/strings.xml @@ -4,13 +4,13 @@ 상태 기본 basal 양 임시 basal 양 - 모두 주입됨 + 총 주입량 종료 1. 2. - 재실행 + 주입 재개 중지 중지됨 실행중 diff --git a/pump/eopatch/src/main/res/values-lt-rLT/strings.xml b/pump/eopatch/src/main/res/values-lt-rLT/strings.xml index 91a171798d..4932428619 100644 --- a/pump/eopatch/src/main/res/values-lt-rLT/strings.xml +++ b/pump/eopatch/src/main/res/values-lt-rLT/strings.xml @@ -8,12 +8,12 @@ Priminimas apie Patch galiojimą Garsinio signalo priminimai val:min - BLE būsena + BLE statusas Serijos nr. Partijos nr. Paleidimo data & laikas Galiojimo laikas - Būsena + Statusas Pagrindinė valandinė bazė Laikina valandinė bazė Iš viso suleista @@ -95,7 +95,7 @@ Sustabdžius dabartinis bolusas & laikina bazė bus atšaukti. \n\nDydis : %1$s v/val\nLiko laiko : %2$s\n& Liko insulino : %3$.2f v Sustabdžius dabartinis bolusas bus atšauktas. \n\nLiko insulino : %1$.2f v Sustabdžius dabartinė laikina bazė bus atšaukta. \n\nDydis : %1$s v/val\nLiko laiko : %2$s - Insulino leidimas bus sustabdytas. + Insulino tiekimas bus sustabdytas. Baigti naudoti/Keisti Patch Aktyvuoti Patch Baigti naudoti Patch @@ -121,8 +121,8 @@ Bazės sustabdymo laikas Atnaujinti insulino tiekimą Pompos sustabdymo pabaiga.\nNorėdami atnaujinti insulino tiekimą, spauskite \'Patvirtinti\'.\n\n[Caution]\nInsulino tiekimas nebus atnaujintas, kol nepaspausite \'Patvirtinti\'.\nNepaspaudus \'Patvirtinti\', gali kilti hiperglikemija. - Insulino leidimas sustabdytas. - Insulino leidimas atnaujintas. + Insulino tiekimas sustabdytas. + Insulino tiekimas atnaujintas. Užbaigti liko laiko: %1$s:%2$s Kitas diff --git a/pump/medtronic/src/main/res/values-lt-rLT/strings.xml b/pump/medtronic/src/main/res/values-lt-rLT/strings.xml index 9105021cd0..588cdaa0ae 100644 --- a/pump/medtronic/src/main/res/values-lt-rLT/strings.xml +++ b/pump/medtronic/src/main/res/values-lt-rLT/strings.xml @@ -66,7 +66,7 @@ Gauti istoriją - Lapas %1$d Gauti pompos laiką Nustatyti pompos laiką - Parodyti baterijos būseną + Parodyti baterijos statusą Gauti parametrus Gauti pompos modelį Gauti bazės profilį @@ -94,5 +94,5 @@ RL statistika Tipas: Paspausta atnaujinti - Planuotas būklės naujinimas + Statuso naujinimas diff --git a/pump/omnipod-common/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/common/ui/wizard/common/activity/OmnipodWizardActivityBase.kt b/pump/omnipod-common/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/common/ui/wizard/common/activity/OmnipodWizardActivityBase.kt index a2c2e768ae..7b56898a59 100644 --- a/pump/omnipod-common/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/common/ui/wizard/common/activity/OmnipodWizardActivityBase.kt +++ b/pump/omnipod-common/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/common/ui/wizard/common/activity/OmnipodWizardActivityBase.kt @@ -3,10 +3,10 @@ package info.nightscout.androidaps.plugins.pump.omnipod.common.ui.wizard.common. import androidx.appcompat.app.AlertDialog import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment -import dagger.android.support.DaggerAppCompatActivity import info.nightscout.androidaps.plugins.pump.omnipod.common.R +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity -abstract class OmnipodWizardActivityBase : DaggerAppCompatActivity() { +abstract class OmnipodWizardActivityBase : TranslatedDaggerAppCompatActivity() { override fun onBackPressed() = exitActivityAfterConfirmation() diff --git a/pump/omnipod-common/src/main/res/values-lt-rLT/strings.xml b/pump/omnipod-common/src/main/res/values-lt-rLT/strings.xml index 76c64eb611..ccecd9b258 100644 --- a/pump/omnipod-common/src/main/res/values-lt-rLT/strings.xml +++ b/pump/omnipod-common/src/main/res/values-lt-rLT/strings.xml @@ -6,9 +6,9 @@ Veiksmai Įrankiai Aktyvuoti Pod - Išjungti Pod + Deaktyvuoti Pod Baigti naudoti Pod - Jei baigsite naudoti Pod, ryšio su juo nebebus galima atkurti. Darykite tai tik tada, kai pastoviai nepavyksta užmegzti ryšio. Jei ryšys visgi yra, naudokite Deaktyvuoti Pod funkciją. \n\nJei norite tęsti, pašalinkite Pod nuo savo kūno! + Jei baigsite naudoti Pod, ryšio su juo nebebus galima atkurti. Darykite tai tik tada, kai nebepavyksta užmegzti ryšio. Jei ryšys visgi yra, naudokite Deaktyvuoti Pod funkciją. \n\nJei norite tęsti, pašalinkite Pod nuo savo kūno! Paleisti bandomąjį signalą Bandomasis signalas paleistas… Pod istorija @@ -16,47 +16,47 @@ Nėra aktyvaus Pod Nepavyko nustatyti bazės profilio. Bolusas nepavyko. - Nepavyko nustatyti bazinio profilio: gautas tuščias profilis. Įsitikinkite, kad aktyvavote savo bazės profilį. + Nepavyko nustatyti bazės: gautas tuščias profilis. Įsitikinkite, kad aktyvavote savo bazės profilį. Nėra aktyvaus profilio. Įsitikinkite, kad bazės profilis aktyvuotas. Nepalaikoma pasirinktinė komanda: %1$s - Nepavyko atnaujinti būsenos - Nepavyko atnaujinti būsenos paleidžiant - Nepavyko nutildyti įspėjimų - Nepavyko sustabdyti suleidimo + Nepavyko atnaujinti statuso + Nepavyko atnaujinti statuso paleidžiant + Nepavyko nutildyti perspėjimų + Nepavyko sustabdyti insulino tiekimo Nepavyko nustatyti laiko - Nepavyko atnaujinti suleidimo + Nepavyko atnaujinti insulino tiekimo Nepavyko inicijuoti Pod Nepavyko įvesti kaniulės - Pod aktyvavimo laikas buvo viršytas. Šis Pod nebegali būti aktyvuotas. - Nepavyko patvirtinti aktyvinimo eigos. Pabandykite kartoti. - Pod sustabdyta + Pod aktyvavimo laikas baigėsi. Šis Pod nebegali būti aktyvuotas. + Nepavyko patikrinti aktyvavimo. Pabandykite pakartoti. + Pod sustabdytas Nepavyko paleisti bandomojo signalo - Neatitinka laikas su Pod. Atnaujinkite laiką skirtuke Omnipod. + Pod laikas nesinchronizuotas. Atnaujinkite laiką skirtuke Omnipod. Įvyko netikėta klaida. Prašome pranešti! (%1$s: %2$s). Patvirtinimas - Laikas ir/ar laiko zona Pod pakeista. - Įspėjimo konfigūracija Pod atnaujinta. - Laikas Pod buvo atnaujintas. - Insulino suleidimas buvo sustabdytas. + Pod laikas ir/ar laiko zona pakeista. + Pod perspėjimo konfigūracija atnaujinta. + Pod laikas atnaujintas. + Insulino tiekimas pilnai sustabdytas. Aktyvūs perspėjimai buvo nutildyti. - Insulino suleidimas atnaujintas. + Insulino tiekimas atnaujintas. Nustatyti laiką - Sustabdyta - Atnaujinti suleidimą + Sustabdyti + Atnaujinti insulino tiekimą Pod valdymas - Tylūs perpėjimai - Pod būklė + Nutildyti + Pod statusas Iš viso suleista %1$.2f vv Unikalus ID LOT numeris Eilės Nr. - Pod laikas pasibaigs + Pod naudojimo pabaiga Paskutinis prisijungimas Paskutinis bolusas - Laikina valandinė bazė + Laikina bazė Pagrindinė valandinė bazė Rezervuaras Aktyvūs Pod perspėjimai @@ -71,41 +71,41 @@ Užbaigti Kitas Kartoti - Išjungti Pod + Deaktyvuoti Pod Baigti naudoti Pod Jūs dar nebaigėte visų veiksmų. Ar tikrai norite išeiti? Išeiti - Užpildyti Pod + Užpildykite Pod Inicijuoti Pod Prijunkite Pod - Paruoškite infuzijos vietą. Nuimkite adatos apsauginį dangtelį, pleistro apsaugą ir užklijuokite Pod ant infuzijos vietos.\n\nJei kaniulė atšoks, paspauskite Atšaukti ir pakeiskite Pod.\n\nSpauskite Kitas, norėdami įvesti kaniulę ir pradėti leisti insuliną. - Kai paspausite OK, kaniulė bus įvesta. Įsitikinkite, kad prie infuzijos vietos pritvirtinote Pod. + Paruoškite infuzijos vietą. Nuimkite adatos apsauginį dangtelį, pleistro apsaugą ir priklijuokite Pod infuzijos vietoje.\n\nJei kaniulė iššoko, paspauskite Atšaukti ir pakeiskite Pod.\n\nSpauskite Kitas, norėdami įvesti kaniulę ir pradėti leisti insuliną. + Kai paspausite OK, kaniulė bus įvesta. Įsitikinkite, kad Pod pritvirtinote infuzijos vietoje. Įvesti kaniulę - Mėginkite nustatyti pradinį bazės profilį ir įvesti kaniulę.\n\nKai kaniulė bus sėkmingai įvesta, galite paspausti Kitas. + Nustatomas pradinis bazės profilis ir įvedama kaniulė.\n\nKai kaniulė bus sėkmingai įvesta, spauskite Kitas. Pod aktyvuotas - Naujasis Pod yra dabar aktyvus.\n\nJūsų bazės profilis yra suprogramuotas ir kaniulė įvesta.\n\nPatikrinkite, ar kaniulė buvo tinkamai įvesta, ir, jei jaučiate, kad taip nėra, pakeiskite Pod. + Naujasis Pod yra aktyvus.\n\nJūsų bazės profilis yra suprogramuotas ir kaniulė įvesta.\n\nPatikrinkite, ar kaniulė buvo tinkamai įvesta, jei taip nėra, pakeiskite Pod. - Išjungti Pod - SpustelėkiteKitas, kad išjungtumėte Pod.\n\nPastaba: Tai sustabdys insulino leidimą ir išjungs Pod. - Pod išjungiamas - Pod išjungiamas.\n\nKai išjungimas bus sėkmingai baigtas, galite spustelėti Kitas. - Pod išjungtas - Pod išjungtas.\n\nNuimkite Pod nuo kūno ir jį išmeskite. + Deaktyvuoti Pod + SpustelėkiteKitas, kad deaktyvuotumėte Pod.\n\nPastaba: Tai sustabdys insulino leidimą ir išjungs Pod. + Pod deaktyvuojamas + Pod deaktyvuojamas.\n\nKai veiksmas bus sėkmingai baigtas, spauskite Kitas. + Pod deaktyvuotas + Pod deaktyvuotas.\n\nNuimkite Pod nuo kūno ir jį išmeskite. Pod baigtas naudoti Pod statusas atmestas. Insulino tiekimas nesustabdytas, nes Pod nebuvo teisingai deaktyvuotas. \n\nPašalinkite Pod nuo savo kūno ir jį utilizuokite. - Jei baigsite naudoti Pod, ryšio su juo nebebus galima užmegzti. Darykite tai tik tada, kai visi bandymai užmegzti ryšį nepavyksta. Ar esate tikri, kad norite atmesti Pod? + Jei baigsite naudoti Pod, ryšio su juo nebebus galima užmegzti. Darykite tai tik tada, kai užmegzti ryšio nebepavyksta. Ar esate tikri, kad norite baigti naudoti Pod? Baigti naudoti Pod Boluso signalai įjungti Bazės signalai įjungti SMB signalai įjungti Laikinos bazės signalai įjungti - Omnipod skirtuke rodyti mygtuką Sustabdyti tiekimą + Omnipod skirtuke rodyti mygtuką Sustabdyti insulino tiekimą Vasaros laiko/Laiko juostos aptikimas įgalintas Galiojimo pabaigos priminimas - Valandos prieš išsijungimą - Įgalintas įspėjimas apie žemą rezervuaro lygį + Val. iki išsijungimo + Įjungtas perspėjimas apie senkantį rezervuarą Vienetų skaičius Automatiškai nutildyti Pod perspėjimus Kiti @@ -117,12 +117,12 @@ Įjungtas garsas neaiškiems bolusų perspėjimams Nėra aktyvaus Pod - Vykdomas nustatymas (laukiama Pod aktyvavimo) - Vykdomas nustatymas (laukiama kaniulės įvedimo) + Nustatoma (laukiama Pod aktyvavimo) + Nustatoma (laukiama kaniulės įvedimo) Vykdoma Sustabdyta Pod klaida - Viršytas aktyvinimo laikas + Baigėsi aktyvavimo laikas Neaktyvus Pod klaida: %1$03d %2$s @@ -134,34 +134,34 @@ Atšaukti laikiną bazę (vidinis valdiklis) Atšaukti laikiną bazę Nustatyti bazės tvarkaraštį - Gauti Pod būseną + Gauti Pod statusą Gauti Pod informaciją Nustatyti laiką Konfigūruoti perspėjimus - Tylūs perpėjimai - Sustabdyti suleidimą - Atnaujinti suleidimą - Nežinomas Įrašas + Nutildyti + Sustabdyti insulino tiekimą + Atnaujinti insulino tiekimą + Nežinomas įrašas Inicijuoti Pod Įvesti kaniulę Skaityti Pulse žurnalą - Nustatykite netikrą laikiną bazę, nes Pod sustabdyta - Atšaukite netikrą laikiną bazę, kuri buvo nustatyta dėl to, kad Pod buvo sustabdyta + Nustatyta netikra laikina bazė, nes Pod sustabdytas + Atšaukti netikrą laikiną bazę, kuri buvo nustatyta dėl to, kad Pod buvo sustabdytas Laikina bazė padalinta dėl nežinomos klaidos atšaukiant ją Signalo nustatymai Paleisti bandomąjį signalą - Priminimas apie suporavimo pabaigą - Priminimas apie sąrankos pabaigą - Pod galiojimas greitai baigsis - Pod galiojimas greitai baigsis + Priminimas užbaigti susiejimą + Priminimas užbaigti sąranką + Pod naudojimo laikas greitai baigsis + Pod naudojimo laikas greitai baigsis Išjungimas yra neišvengiamas - Žemas rezervuaro lygis + Senka rezervuaras Nežinomas perspėjimas Nėra aktyvaus Pod - Pask. prisijung.: prieš %1$d min - Pask.Bolusas: %1$s @ %2$s + Pask. prijung: prieš %1$d min + PaskBolusas: %1$s @ %2$s Laik.bazė: %1$s Ištęstas: %1$s Rezerv: %1$svv diff --git a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/OmnipodDashPumpPlugin.kt b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/OmnipodDashPumpPlugin.kt index e302094453..8f4e0fe4b9 100644 --- a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/OmnipodDashPumpPlugin.kt +++ b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/OmnipodDashPumpPlugin.kt @@ -328,6 +328,7 @@ class OmnipodDashPumpPlugin @Inject constructor( podStateManager.updateActiveCommand() .map { handleCommandConfirmation(it) } .ignoreElement(), + verifyPumpState(), checkPodKaput(), ) ) @@ -572,9 +573,7 @@ class OmnipodDashPumpPlugin @Inject constructor( .bolusDelivered(0.0) .comment(rh.gs(R.string.omnipod_dash_not_enough_insulin)) } - if (podStateManager.deliveryStatus == DeliveryStatus.BOLUS_AND_BASAL_ACTIVE || - podStateManager.deliveryStatus == DeliveryStatus.BOLUS_AND_TEMP_BASAL_ACTIVE - ) { + if (podStateManager.deliveryStatus?.bolusDeliveringActive() == true) { return PumpEnactResult(injector) .success(false) .enacted(false) @@ -632,14 +631,7 @@ class OmnipodDashPumpPlugin @Inject constructor( ) } else { if (podStateManager.activeCommand != null) { - val sound = - if (sp.getBoolean( - info.nightscout.androidaps.plugins.pump.omnipod.common.R.string - .key_omnipod_common_notification_uncertain_bolus_sound_enabled, true - ) - ) info.nightscout.core.ui.R.raw.boluserror - else 0 - + val sound = if (hasBolusErrorBeepEnabled()) info.nightscout.core.ui.R.raw.boluserror else 0 showErrorDialog(rh.gs(R.string.bolus_delivery_status_uncertain), sound) } } @@ -714,7 +706,7 @@ class OmnipodDashPumpPlugin @Inject constructor( } val percent = (waited.toFloat() / estimatedDeliveryTimeSeconds) * 100 updateBolusProgressDialog( - rh.gs(info.nightscout.pump.common.R.string.bolus_delivered_so_far, Round.roundTo(percent*requestedBolusAmount/100, PodConstants.POD_PULSE_BOLUS_UNITS), requestedBolusAmount), + rh.gs(info.nightscout.pump.common.R.string.bolus_delivered_so_far, Round.roundTo(percent * requestedBolusAmount / 100, PodConstants.POD_PULSE_BOLUS_UNITS), requestedBolusAmount), percent.toInt() ) } @@ -897,9 +889,7 @@ class OmnipodDashPumpPlugin @Inject constructor( private fun observeNoActiveTempBasal(): Completable { return Completable.defer { - if (podStateManager.deliveryStatus !in - arrayOf(DeliveryStatus.TEMP_BASAL_ACTIVE, DeliveryStatus.BOLUS_AND_TEMP_BASAL_ACTIVE) - ) { + if (podStateManager.deliveryStatus?.tempBasalActive() == false) { // TODO: what happens if we try to cancel nonexistent temp basal? aapsLogger.info(LTag.PUMP, "No temporary basal to cancel") Completable.complete() @@ -946,6 +936,13 @@ class OmnipodDashPumpPlugin @Inject constructor( return sp.getBoolean(info.nightscout.androidaps.plugins.pump.omnipod.common.R.string.key_omnipod_common_basal_beeps_enabled, false) } + private fun hasBolusErrorBeepEnabled(): Boolean { + return sp.getBoolean( + info.nightscout.androidaps.plugins.pump.omnipod.common.R.string + .key_omnipod_common_notification_uncertain_bolus_sound_enabled, true + ) + } + override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult { if (!podStateManager.tempBasalActive && pumpSync.expectedPumpState().temporaryBasal == null @@ -1323,6 +1320,7 @@ class OmnipodDashPumpPlugin @Inject constructor( podStateManager.updateActiveCommand() .map { handleCommandConfirmation(it) } .ignoreElement(), + verifyPumpState(), checkPodKaput(), refreshOverview(), post, @@ -1488,6 +1486,40 @@ class OmnipodDashPumpPlugin @Inject constructor( } } + private fun verifyPumpState(): Completable = Completable.defer { + aapsLogger.debug(LTag.PUMP, "verifyPumpState, AAPS: ${pumpSync.expectedPumpState().temporaryBasal} Pump: ${podStateManager.deliveryStatus}") + val tbr = pumpSync.expectedPumpState().temporaryBasal + if (tbr != null && podStateManager.deliveryStatus?.basalActive() == true) { + aapsLogger.error(LTag.PUMP, "AAPS expected a TBR running but pump has no TBR running! AAPS: ${pumpSync.expectedPumpState().temporaryBasal} Pump: ${podStateManager.deliveryStatus}") + // Alert user + val sound = if (hasBolusErrorBeepEnabled()) info.nightscout.core.ui.R.raw.boluserror else 0 + showErrorDialog(rh.gs(R.string.temp_basal_out_of_sync), sound) + // Sync stopped basal with AAPS + val ret = pumpSync.syncStopTemporaryBasalWithPumpId( + System.currentTimeMillis(), // Note: It would be nice if TBR end could be estimated, but this will add a lot of complexity + tbr.id, + PumpType.OMNIPOD_DASH, + serialNumber() + ) + aapsLogger.info(LTag.PUMP, "syncStopTemporaryBasalWithPumpId ret=$ret pumpId=${tbr.id}") + podStateManager.tempBasal = null + } else if (tbr == null && podStateManager.deliveryStatus?.tempBasalActive() == true) { + aapsLogger.error(LTag.PUMP, "AAPS expected no TBR running but pump has a TBR running! AAPS: ${pumpSync.expectedPumpState().temporaryBasal} Pump: ${podStateManager.deliveryStatus}") + // Alert user + val sound = if (hasBolusErrorBeepEnabled()) info.nightscout.core.ui.R.raw.boluserror else 0 + showErrorDialog(rh.gs(R.string.temp_basal_out_of_sync), sound) + // If this is reached is reached there is probably a something wrong with the time (maybe it has changed?). + // No way to calculate the TBR end time and update pumpSync properly. + // Cancel TBR running on Pump + return@defer observeNoActiveTempBasal() + .concatWith(podStateManager.updateActiveCommand() + .map { handleCommandConfirmation(it) } + .ignoreElement()) + } + + return@defer Completable.complete() + } + private fun showErrorDialog(message: String, sound: Int) { uiInteraction.runAlarm(message, rh.gs(info.nightscout.core.ui.R.string.error), sound) } diff --git a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Connection.kt b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Connection.kt index 55a47cb332..177f8ed552 100644 --- a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Connection.kt +++ b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Connection.kt @@ -56,6 +56,8 @@ class Connection( private val bluetoothManager: BluetoothManager? = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager? + private var _connectionWaitCond: ConnectionWaitCondition? = null + @Volatile var session: Session? = null @@ -65,6 +67,7 @@ class Connection( @Synchronized fun connect(connectionWaitCond: ConnectionWaitCondition) { aapsLogger.debug("Connecting connectionWaitCond=$connectionWaitCond") + _connectionWaitCond = connectionWaitCond podState.connectionAttempts++ podState.bluetoothConnectionState = OmnipodDashPodStateManager.BluetoothConnectionState.CONNECTING val autoConnect = false @@ -80,6 +83,7 @@ class Connection( val before = SystemClock.elapsedRealtime() if (waitForConnection(connectionWaitCond) !is Connected) { podState.bluetoothConnectionState = OmnipodDashPodStateManager.BluetoothConnectionState.DISCONNECTED + _connectionWaitCond = null throw FailedToConnectException(podDevice.address) } val waitedMs = SystemClock.elapsedRealtime() - before @@ -117,21 +121,24 @@ class Connection( cmdBleIO.hello() cmdBleIO.readyToRead() dataBleIO.readyToRead() + _connectionWaitCond = null } @Synchronized fun disconnect(closeGatt: Boolean) { aapsLogger.debug(LTag.PUMPBTCOMM, "Disconnecting closeGatt=$closeGatt") - podState.bluetoothConnectionState = OmnipodDashPodStateManager.BluetoothConnectionState.DISCONNECTED - if (closeGatt) { - gattConnection?.close() - gattConnection = null - } else { + if (closeGatt == false && gattConnection != null) { + // Disconnect first, then close gatt gattConnection?.disconnect() + } else { + // Call with closeGatt=true only when ble is already disconnected or there is no connection + gattConnection?.close() + bleCommCallbacks.resetConnection() + gattConnection = null + session = null + msgIO = null + podState.bluetoothConnectionState = OmnipodDashPodStateManager.BluetoothConnectionState.DISCONNECTED } - bleCommCallbacks.resetConnection() - session = null - msgIO = null } private fun waitForConnection(connectionWaitCond: ConnectionWaitCondition): ConnectionState { @@ -200,7 +207,14 @@ class Connection( // This will be called from a different thread !!! override fun onConnectionLost(status: Int) { aapsLogger.info(LTag.PUMPBTCOMM, "Lost connection with status: $status") - disconnect(false) + // Check if waiting for connection, if so, stop waiting + _connectionWaitCond?.stopConnection?.let { + if (it.count > 0) { + _connectionWaitCond?.stopConnection?.countDown() + } + } + // BLE disconnected, so need to close gatt + disconnect(true) } companion object { diff --git a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/util/ProgramTempBasalUtil.kt b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/util/ProgramTempBasalUtil.kt index fcb83b64de..aab40624c9 100644 --- a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/util/ProgramTempBasalUtil.kt +++ b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/util/ProgramTempBasalUtil.kt @@ -22,10 +22,15 @@ object ProgramTempBasalUtil { fun mapTempBasalToTenthPulsesPerSlot(durationInSlots: Int, rateInUnitsPerHour: Double): ShortArray { val pulsesPerHour = (rateInUnitsPerHour * 20).roundToInt().toShort() val tenthPulsesPerSlot = ShortArray(durationInSlots) - var i = 0 - while (durationInSlots > i) { - tenthPulsesPerSlot[i] = (roundToHalf(pulsesPerHour / 2.0) * 10).toInt().toShort() - i++ + val tenthPulsesPerSlotShort = if (pulsesPerHour == 0.toShort() && durationInSlots > 4) { + // Workaround for 0.0 U/h long temp basals being cancelled by pod + // This will result in a 0.01 U/h temp basal for 0temps > 120 minutes + 1.toShort() + } else { + (roundToHalf(pulsesPerHour / 2.0) * 10).toInt().toShort() + } + for (i in tenthPulsesPerSlot.indices) { + tenthPulsesPerSlot[i] = tenthPulsesPerSlotShort } return tenthPulsesPerSlot } diff --git a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/definition/DeliveryStatus.kt b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/definition/DeliveryStatus.kt index 106a3f3ffe..cb3b3eb016 100644 --- a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/definition/DeliveryStatus.kt +++ b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/definition/DeliveryStatus.kt @@ -16,6 +16,10 @@ enum class DeliveryStatus(override val value: Byte) : HasValue { return value in arrayOf(BOLUS_AND_BASAL_ACTIVE.value, BOLUS_AND_TEMP_BASAL_ACTIVE.value) } + fun basalActive(): Boolean { + return value in arrayOf(BOLUS_AND_BASAL_ACTIVE.value, BASAL_ACTIVE.value) + } + fun tempBasalActive(): Boolean { return value in arrayOf(BOLUS_AND_TEMP_BASAL_ACTIVE.value, TEMP_BASAL_ACTIVE.value) } diff --git a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt index 3fde3b3070..9fe594192b 100644 --- a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt +++ b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt @@ -3,6 +3,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.history import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType.SET_BOLUS import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType.SET_TEMPORARY_BASAL +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.PodConstants.Companion.POD_PULSE_BOLUS_UNITS import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.CommandConfirmationDenied import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.CommandConfirmationSuccess import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.CommandSendingFailure @@ -57,6 +58,7 @@ class DashHistory @Inject constructor( tempBasalRecord: TempBasalRecord? = null, bolusRecord: BolusRecord? = null, basalProfileRecord: BasalValuesRecord? = null, + totalAmountDeliveredRecord: Double? = null, resolveResult: ResolvedResult? = null, resolvedAt: Long? = null ): Single = Single.defer { @@ -79,6 +81,7 @@ class DashHistory @Inject constructor( tempBasalRecord = tempBasalRecord, bolusRecord = bolusRecord, basalProfileRecord = basalProfileRecord, + totalAmountDelivered = totalAmountDeliveredRecord, initialResult = initialResult, resolvedResult = resolveResult, resolvedAt = resolvedAt @@ -99,18 +102,23 @@ class DashHistory @Inject constructor( logger.error(LTag.PUMP, "HistoryId not found to for updating from state") return@defer Completable.complete() } - when (podState.getCommandConfirmationFromState()) { - CommandSendingFailure -> + + val setTotalAmountDelivered = dao.setTotalAmountDelivered(historyId, podState.pulsesDelivered?.times(POD_PULSE_BOLUS_UNITS)) + + val commandConfirmation = when (podState.getCommandConfirmationFromState()) { + CommandSendingFailure -> dao.setInitialResult(historyId, InitialResult.FAILURE_SENDING) CommandSendingNotConfirmed -> dao.setInitialResult(historyId, InitialResult.SENT) - CommandConfirmationDenied -> + CommandConfirmationDenied -> markFailure(historyId) CommandConfirmationSuccess -> dao.setInitialResult(historyId, InitialResult.SENT) .andThen(markSuccess(historyId)) - NoActiveCommand -> + NoActiveCommand -> Completable.complete() } + + Completable.concat(listOf(setTotalAmountDelivered, commandConfirmation)) } } diff --git a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt index 40cb5a7d24..7d26b2a9a6 100644 --- a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt +++ b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt @@ -9,6 +9,7 @@ data class HistoryRecord( val commandType: OmnipodCommandType, val initialResult: InitialResult, val record: Record?, + val totalAmountDelivered: Double?, val resolvedResult: ResolvedResult?, val resolvedAt: Long? ) { diff --git a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/DashHistoryDatabase.kt b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/DashHistoryDatabase.kt index 5482e8a669..d5e16482c0 100644 --- a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/DashHistoryDatabase.kt +++ b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/DashHistoryDatabase.kt @@ -18,7 +18,7 @@ abstract class DashHistoryDatabase : RoomDatabase() { companion object { - const val VERSION = 3 + const val VERSION = 4 fun build(context: Context) = Room.databaseBuilder( diff --git a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordDao.kt b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordDao.kt index 13ba7b1953..0ad9934a1c 100644 --- a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordDao.kt +++ b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordDao.kt @@ -36,4 +36,7 @@ abstract class HistoryRecordDao { @Query("UPDATE historyrecords SET initialResult = :initialResult WHERE id = :id ") abstract fun setInitialResult(id: Long, initialResult: InitialResult): Completable + + @Query("UPDATE historyrecords SET totalAmountDelivered = :totalAmountDelivered WHERE id = :id ") + abstract fun setTotalAmountDelivered(id: Long, totalAmountDelivered: Double?): Completable } diff --git a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordEntity.kt b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordEntity.kt index 1993f525cb..a4245331ad 100644 --- a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordEntity.kt +++ b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordEntity.kt @@ -27,6 +27,7 @@ data class HistoryRecordEntity( @Embedded(prefix = "tempBasalRecord_") val tempBasalRecord: TempBasalRecord?, @Embedded(prefix = "bolusRecord_") val bolusRecord: BolusRecord?, @Embedded(prefix = "basalprofile_") val basalProfileRecord: BasalValuesRecord?, + val totalAmountDelivered: Double?, val resolvedResult: ResolvedResult?, val resolvedAt: Long? ) diff --git a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/mapper/HistoryMapper.kt b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/mapper/HistoryMapper.kt index 26827110b2..da8a26145e 100644 --- a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/mapper/HistoryMapper.kt +++ b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/mapper/HistoryMapper.kt @@ -13,6 +13,7 @@ class HistoryMapper { initialResult = entity.initialResult, commandType = entity.commandType, record = entity.bolusRecord ?: entity.tempBasalRecord ?: entity.basalProfileRecord, + totalAmountDelivered = entity.totalAmountDelivered, resolvedResult = entity.resolvedResult, resolvedAt = entity.resolvedAt ) diff --git a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/DashPodHistoryActivity.kt b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/DashPodHistoryActivity.kt index 9328ac9e71..dc66e19bd9 100644 --- a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/DashPodHistoryActivity.kt +++ b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/DashPodHistoryActivity.kt @@ -11,7 +11,6 @@ import android.widget.Spinner import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import dagger.android.support.DaggerAppCompatActivity import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType import info.nightscout.androidaps.plugins.pump.omnipod.dash.R import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.DashHistory @@ -21,6 +20,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.History import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.InitialResult import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.ResolvedResult import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.TempBasalRecord +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.utils.DateTimeUtil import info.nightscout.interfaces.pump.defs.PumpType import info.nightscout.pump.common.defs.PumpHistoryEntryGroup @@ -33,7 +33,7 @@ import java.util.Calendar import java.util.GregorianCalendar import javax.inject.Inject -class DashPodHistoryActivity : DaggerAppCompatActivity() { +class DashPodHistoryActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var dashHistory: DashHistory @Inject lateinit var aapsSchedulers: AapsSchedulers @@ -213,6 +213,7 @@ class DashPodHistoryActivity : DaggerAppCompatActivity() { holder.timeView.text = DateTimeUtil.toStringFromTimeInMillis(it.displayTimestamp()) setValue(it, holder.valueView) setType(it, holder.typeView) + setAmount(it, holder.amountView) } } @@ -297,6 +298,12 @@ class DashPodHistoryActivity : DaggerAppCompatActivity() { setTextViewColor(check_result = false, valueView, historyEntry) } + private fun setAmount(historyEntry: HistoryRecord, amountView: TextView) { + amountView.text = historyEntry.totalAmountDelivered?.let { rh.gs(R.string.omnipod_common_history_total_delivered, it) } + // Set some color + setTextViewColor(check_result = false, amountView, historyEntry) + } + override fun getItemCount(): Int { return historyList.size } @@ -306,6 +313,7 @@ class DashPodHistoryActivity : DaggerAppCompatActivity() { val timeView: TextView = itemView.findViewById(R.id.omnipod_history_time) val typeView: TextView = itemView.findViewById(R.id.omnipod_history_source) val valueView: TextView = itemView.findViewById(R.id.omnipod_history_description) + val amountView: TextView = itemView.findViewById(R.id.omnipod_history_amount) } } diff --git a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/DashPodManagementActivity.kt b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/DashPodManagementActivity.kt index 34fd85af45..36f4306b0a 100644 --- a/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/DashPodManagementActivity.kt +++ b/pump/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/DashPodManagementActivity.kt @@ -4,7 +4,6 @@ import android.content.Context import android.content.Intent import android.os.Bundle import dagger.android.HasAndroidInjector -import dagger.android.support.DaggerAppCompatActivity import info.nightscout.androidaps.plugins.pump.omnipod.common.queue.command.CommandPlayTestBeep import info.nightscout.androidaps.plugins.pump.omnipod.common.ui.wizard.activation.PodActivationWizardActivity import info.nightscout.androidaps.plugins.pump.omnipod.dash.databinding.OmnipodDashPodManagementBinding @@ -12,6 +11,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definitio import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager import info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.wizard.activation.DashPodActivationWizardActivity import info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.wizard.deactivation.DashPodDeactivationWizardActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.ui.dialogs.OKDialog import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.interfaces.queue.Callback @@ -26,7 +26,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import javax.inject.Inject -class DashPodManagementActivity : DaggerAppCompatActivity() { +class DashPodManagementActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var commandQueue: CommandQueue diff --git a/pump/omnipod-dash/src/main/res/layout/omnipod_dash_pod_history_item.xml b/pump/omnipod-dash/src/main/res/layout/omnipod_dash_pod_history_item.xml index 93b4bee75a..771b8a6488 100644 --- a/pump/omnipod-dash/src/main/res/layout/omnipod_dash_pod_history_item.xml +++ b/pump/omnipod-dash/src/main/res/layout/omnipod_dash_pod_history_item.xml @@ -14,18 +14,36 @@ android:text="@string/omnipod_dash_history_item_date" android:textSize="12sp" /> - + - + - \ No newline at end of file + + + + + + + diff --git a/pump/omnipod-dash/src/main/res/values-fr-rFR/strings.xml b/pump/omnipod-dash/src/main/res/values-fr-rFR/strings.xml index 4688c8d7de..9f6bd5f711 100644 --- a/pump/omnipod-dash/src/main/res/values-fr-rFR/strings.xml +++ b/pump/omnipod-dash/src/main/res/values-fr-rFR/strings.xml @@ -42,7 +42,7 @@ Paramétrage du profil OK Arrêt de la livraison n\'est pas confirmé! Veuillez actualiser manuellement le statut du Pod depuis l\'onglet Omnipod et reprendre la livraison si nécessaire. Injection d\'insuline suspendue - Le fuseau horaire du pod est différent du fuseau horaire du téléphone. Le débit de basal est incorrect. Changez de profil pour le corriger + L\'heure du pod est différente de l\'heure du téléphone. Changez de profil pour corriger le débit de basal. Échec de définition du nouveau profil de basal. Livraison suspendue Le paramétrage du profil de basal a peut-être échoué. L\'injection peut être suspendue ! Actualisez manuellement l\'état du Pod à partir de l\'onglet Omnipod et reprenez l\'injection si nécessaire. Statut de l\'injection du bolus incertain. Rafraîchir le statut du pod pour confirmer ou refuser. diff --git a/pump/omnipod-dash/src/main/res/values-it-rIT/strings.xml b/pump/omnipod-dash/src/main/res/values-it-rIT/strings.xml index 80edc8436f..9d84092a8d 100644 --- a/pump/omnipod-dash/src/main/res/values-it-rIT/strings.xml +++ b/pump/omnipod-dash/src/main/res/values-it-rIT/strings.xml @@ -48,4 +48,7 @@ Stato erogazione bolo incerto. Ricarica lo stato del pod per confermare o negare. Controllo stato erogazione L\'impostazione della basale temporanea potrebbe essere fallita. Se una basale temporanea era precedentemente in esecuzione, è stata cancellata. Aggiorna manualmente lo stato del pod dalla scheda Omnipod. + Cancellazione basale temporanea: risultato incerto + Comando resumeDelivery non confermato. Aggiorna lo stato del pod + La cancellazione della basale temporanea potrebbe essere fallita. Se una basale temporanea era precedentemente in esecuzione, potrebbe essere stata cancellata. Aggiorna manualmente lo stato del pod dalla scheda Omnipod. diff --git a/pump/omnipod-dash/src/main/res/values-lt-rLT/strings.xml b/pump/omnipod-dash/src/main/res/values-lt-rLT/strings.xml index 363597419e..942c3bb8d4 100644 --- a/pump/omnipod-dash/src/main/res/values-lt-rLT/strings.xml +++ b/pump/omnipod-dash/src/main/res/values-lt-rLT/strings.xml @@ -9,46 +9,46 @@ Šaltinis Data Tipas: - %1$.2f vv - %1$.2f vv, AV=%2$.1f g - Kiekis: %1$.2f vv, trukmė: %2$d min + %1$.2f v + %1$.2f v, AV=%2$.1f g + Kiekis: %1$.2f v, trukmė: %2$d min - Bluetooth būklė + Bluetooth statusas Bluetooth adresas Programinė įranga %1$s / Bluetooth %2$s Ryšio kokybė - Leidimo būklė + Insulino statusas - Užpildykite naują Pod insulinu, kurio pakaktų 3 dienas.\n\nTaip pat atkreipkite dėmesį į du pyptelėjimus iš Pod, kai pildote. Tai rodo, kad rezervuaras buvo užpildytas mažiausiai 80vv. Visiškai ištuštinkite švirkštą, net jei girdėjote du pyptelėjimus.\n\nUžpildę Pod, spustelėkite Kitas.\n\nPastaba: vykdant šiuos veiksmus, nenuimkite Pod adatos apsauginio dangtelio. - Pabandykite suporuoti naują Pod ir jį užpildyti.\n\nSėkmingai užbaigę inicijavimo procesą, galite spustelėti Kitas. - Įjungtas pranešimų apie pristabdytą leidimą garsas + Užpildykite naują Pod insulinu, kurio pakaktų 3 dienoms.\n\nTaip pat atkreipkite dėmesį į du pyptelėjimus iš Pod, kai pildote. Tai rodo, kad rezervuaras buvo užpildytas mažiausiai 80 v. Visiškai ištuštinkite švirkštą, net jei girdėjote du pyptelėjimus.\n\nUžpildę Pod, spustelėkite Kitas.\n\nPastaba: vykdant šiuos veiksmus, nenuimkite Pod adatos apsauginio dangtelio. + Vyksta naujo Pod susiejimas ir užpildymas.\n\nSėkmingai užsibaigus inicijavimo procesui, spauskite Kitas. + Įjungtas pranešimų apie sustabdytą insulino tiekimą garsas Nepavyko prisijungti prie Pod - Nustatyta, kad suaktyvinama per daug Pod įrenginių + Rasta per daug aktyvių Pod įrenginių Nepavyksta rasti aktyvinamo Pod Bendroji klaida: %1$s Nepavyko išsiųsti komandos Komanda nenusiųsta - Pod pompa negavo komandos + Pod negavo komandos Nežinoma komandos būklė - Kiekis: %1$.2f vv, trukmė: %2$d min - %1$.2f vv - Insulino leidimas sustabdytas + Kiekis: %1$.2f v, trukmė: %2$d min + %1$.2f v + Insulino tiekimas sustabdytas Ryšys su Pod prarastas - Kitas bolusas yra jau leidžiamas - Rezervuare nėra pakankamai insulino + Kitas bolusas jau leidžiamas + Rezervuare nepakanka insulino Nepatvirtinta komanda - Užklausta vartotojo + Vartotojo užklausa Profilis nustatytas OK - Insulino tiekimo sustabdymas nepatvirtintas! Rankiniu būdu atnaujinkite Pod būseną Omnipod skirtuke ir, jei reikia, atnaujinkite insulino leidimą. - Insulino leidimas sustabdytas + Insulino tiekimo sustabdymas nepatvirtintas! Rankiniu būdu atnaujinkite Pod statusą Omnipod skirtuke ir, jei reikia, atnaujinkite insulino tiekimą. + Insulino tiekimas sustabdytas Pod laiko zona skiriasi nuo telefono. Bazė neteisinga. Atlikite profilio keitimą Klaida nustatant naują bazės profilį. Insulinas netiekiamas - Gali būti, kad bazės profilis nebuvo nustatytas. Insulino leidimas gali būti nutrauktas! Rankiniu būdu atnaujinkite Pod būseną Omnipod skirtuke ir, jei reikia, atnaujinkite insulino leidimą. + Gali būti, kad bazės profilis nebuvo nustatytas. Insulino tiekimas gali būti nutrauktas! Rankiniu būdu atnaujinkite Pod statusą Omnipod skirtuke ir, jei reikia, atnaujinkite insulino tiekimą. Boluso suleidimo statusas nežinomas. Atnaujinkite Pod statusą patvirtinimui ar atmetimui. - Tikrinama insulino tiekimo būsena - Gali būti, kad nepavyko nustatyti laikinos valandinės bazės. Jei laikina bazė buvo nustatyta anksčiau, ji yra atšaukta. Rakiniu būdu atnaujinkite Pod būseną Omnipod skirtuke. + Tikrinamas insulino tiekimo statusas + Gali būti, kad nepavyko nustatyti laikinos valandinės bazės. Jei laikina bazė buvo nustatyta anksčiau, ji yra atšaukta. Rankiniu būdu atnaujinkite Pod statusą Omnipod skirtuke. Laikinos bazės atšaukimo rezultatas nežinomas Komanda atnaujintiTiekimą nepatvirtinta. Atnaujinkite Pod statusą - Gali būti, kad nepavyko atšaukti laikinos bazės. Jei laikina bazė buvo pradėta anksčiau, ji gali būti jau atšaukta. Rakiniu būdu atnaujinkite Pod būseną Omnipod skirtuke. + Gali būti, kad nepavyko atšaukti laikinos bazės. Jei laikina bazė buvo pradėta anksčiau, ji gali būti jau atšaukta. Rankiniu būdu atnaujinkite Pod statusą Omnipod skirtuke. diff --git a/pump/omnipod-dash/src/main/res/values/strings.xml b/pump/omnipod-dash/src/main/res/values/strings.xml index 37dfad1a38..23e796192c 100644 --- a/pump/omnipod-dash/src/main/res/values/strings.xml +++ b/pump/omnipod-dash/src/main/res/values/strings.xml @@ -11,12 +11,12 @@ Pod History Description + Amount Source Date Type: %1$.2f U %1$.2f U, CH=%2$.1f g - Rate: %1$.2f U, duration: %2$d minutes Bluetooth Status @@ -44,9 +44,10 @@ Command not sent Command not received by the pod Unknown state for the command - Rate: %1$.2f U, duration: %2$d minutes + %1$.2f U/h, %2$d minutes %1$.2f U Insulin delivery is suspended + Total delivered: %1$.2f U Lost connection to pod Another bolus is being delivered Not enough insulin left in the reservoir @@ -60,6 +61,7 @@ Failed to set the new basal profile. Delivery suspended Setting basal profile might have failed. Delivery might be suspended! Please manually refresh the Pod status from the Omnipod tab and resume delivery if needed. Bolus delivery status uncertain. Refresh pod status to confirm or deny. + Temp basal status not as expected! If a temp basal was previously running, it has been cancelled. Please check delivered insulin and pod history Checking delivery status Setting temp basal might have basal failed. If a temp basal was previously running, it has been cancelled. Please manually refresh the Pod status from the Omnipod tab. Cancel temp basal result is uncertain diff --git a/pump/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramTempBasalCommandTest.kt b/pump/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramTempBasalCommandTest.kt index d18db30675..2dc7f24f01 100644 --- a/pump/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramTempBasalCommandTest.kt +++ b/pump/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramTempBasalCommandTest.kt @@ -35,7 +35,39 @@ class ProgramTempBasalCommandTest { .build() Assert.assertArrayEquals( - Hex.decodeHex("024200011C201A0E494E532E0100820A384000009000160EC000000A6B49D200000AEB49D20001DE"), + Hex.decodeHex("024200011C201A0E494E532E0100820A384000009000160EC000000A6B49D200000A6B49D20001E3"), + command.encoded + ) + } + + @Test @Throws(DecoderException::class) fun testZeroTempBasalShort() { + val command = ProgramTempBasalCommand.Builder() + .setUniqueId(37879809) + .setNonce(1229869870) + .setSequenceNumber(7.toShort()) + .setRateInUnitsPerHour(0.0) + .setDurationInMinutes(30.toShort()) + .setProgramReminder(ProgramReminder(true, true, 0.toByte())) + .build() + + Assert.assertArrayEquals( + Hex.decodeHex("024200011C201A0E494E532E01007901384000000000160EC00000016B49D2000001EB49D200815B"), + command.encoded + ) + } + + @Test @Throws(DecoderException::class) fun testZeroTempBasalVeryLong() { + val command = ProgramTempBasalCommand.Builder() + .setUniqueId(37879809) + .setNonce(1229869870) + .setSequenceNumber(7.toShort()) + .setRateInUnitsPerHour(0.0) + .setDurationInMinutes(720.toShort()) + .setProgramReminder(ProgramReminder(true, true, 0.toByte())) + .build() + + Assert.assertArrayEquals( + Hex.decodeHex("024200011C221A10494E532E0100901838400000F0007000160EC00000186B49D20000186B49D2000132"), command.encoded ) } diff --git a/pump/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/ui/ErosPodManagementActivity.kt b/pump/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/ui/ErosPodManagementActivity.kt index 30470298e0..ea2aea4bf2 100644 --- a/pump/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/ui/ErosPodManagementActivity.kt +++ b/pump/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/ui/ErosPodManagementActivity.kt @@ -6,7 +6,6 @@ import android.os.Bundle import android.os.Handler import android.os.HandlerThread import dagger.android.HasAndroidInjector -import dagger.android.support.DaggerAppCompatActivity import info.nightscout.androidaps.plugins.pump.common.events.EventRileyLinkDeviceStatusChange import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog.RileyLinkStatusActivity import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData @@ -24,6 +23,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.eros.manager.AapsOmnipodE import info.nightscout.androidaps.plugins.pump.omnipod.eros.queue.command.CommandReadPulseLog import info.nightscout.androidaps.plugins.pump.omnipod.eros.ui.wizard.activation.ErosPodActivationWizardActivity import info.nightscout.androidaps.plugins.pump.omnipod.eros.ui.wizard.deactivation.ErosPodDeactivationWizardActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.ui.dialogs.OKDialog import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.interfaces.Config @@ -42,7 +42,7 @@ import javax.inject.Inject /** * Created by andy on 30/08/2019 */ -class ErosPodManagementActivity : DaggerAppCompatActivity() { +class ErosPodManagementActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var commandQueue: CommandQueue diff --git a/pump/omnipod-eros/src/main/res/values-lt-rLT/strings.xml b/pump/omnipod-eros/src/main/res/values-lt-rLT/strings.xml index bdce529697..f968bf071e 100644 --- a/pump/omnipod-eros/src/main/res/values-lt-rLT/strings.xml +++ b/pump/omnipod-eros/src/main/res/values-lt-rLT/strings.xml @@ -1,13 +1,13 @@ - Pompos integravimas su Omnipod Eros (senesnis modelis su skaidriu adatos dangteliu). Reikalingas RileyLink įrenginys, su 2.0 ir naujesne programinės įrangos versija. + Omnipod Eros pompos integracija (senesnis modelis su skaidriu adatos dangteliu). Reikalingas RileyLink įrenginys, su 2.0 ar naujesne programinės įrangos versija. Pranešimai Pod valdymo meniu rodyti mygtuką Pulse log - Pod valdymo meniu rodyti mygtuką RileyLink Stats + Pod valdymo meniu rodyti mygtuką RileyLink statistika Įgalinti baterijos keitimo registravimą veiksmuose RileyLink Įjungtas garsas neaiškiems laikinos bazės perspėjimams @@ -19,11 +19,11 @@ Šaltinis Data Tipas: - %1$.2f vv - %1$.2f vv, AV=%2$.1f g - Kiekis: %1$.2f vv, trukmė: %2$d min + %1$.2f v + %1$.2f v, AV=%2$.1f g + Kiekis: %1$.2f v, trukmė: %2$d min - RLBatt: %1$d + RLbat: %1$d RileyLink adresas neteisingas. Ryšio klaida: pranešimo vientisumo patikra nepavyko @@ -41,15 +41,15 @@ Nėra atsakymo iš RileyLink RileyLink ryšys pertrauktas Nėra atsakymo iš Pod - Nepavyko nustatyti bazės profilio. Insulino leidimas gali būti nutrauktas! Rankiniu būdu atnaujinkite Pod būseną Omnipod skirtuke ir, jei reikia, atnaujinkite insulino leidimą. - Gali būti, kad bazės profilis nebuvo nustatytas. Insulino leidimas gali būti nutrauktas! Rankiniu būdu atnaujinkite Pod būseną Omnipod skirtuke ir, jei reikia, atnaujinkite insulino leidimą. - Nepavyko nustatyti bazės profilio. Insulino leidimas buvo sustabdytas! Rankiniu būdu iš Omnipod skirtuko atnaujinkite insulino leidimą. + Nepavyko nustatyti bazės profilio. Insulino tiekimas gali būti nutrauktas! Rankiniu būdu atnaujinkite Pod statusą Omnipod skirtuke ir, jei reikia, atnaujinkite insulino tiekimą. + Gali būti, kad bazės profilis nebuvo nustatytas. Insulino tiekimas gali būti nutrauktas! Rankiniu būdu atnaujinkite Pod statusą Omnipod skirtuke ir, jei reikia, atnaujinkite insulino tiekimą. + Nepavyko nustatyti bazės profilio. Insulino tiekimas sustabdytas! Rankiniu būdu iš Omnipod skirtuko atnaujinkite insulino tiekimą. Gali būti, kad laikinos bazės atšaukti nepavyko. Omnipod skirtuke rankiniu būdu atnaujinkite Pod statusą. - Nepavyko nustatyti laikinos valandinės bazės. Jei laikina bazė buvo pradėta anksčiau, ji gali būti atšaukta. Rakiniu būdu atnaujinkite Pod būseną Omnipod skirtuke. - Gali būti, kad nepavyko nustatyti laikinos valandinės bazės. Jei laikina bazė buvo nustatyta anksčiau, ji yra atšaukta. Rakiniu būdu atnaujinkite Pod būseną Omnipod skirtuke. + Nepavyko nustatyti laikinos valandinės bazės. Jei laikina bazė buvo pradėta anksčiau, ji gali būti atšaukta. Rankiniu būdu atnaujinkite Pod statusą Omnipod skirtuke. + Gali būti, kad nepavyko nustatyti laikinos valandinės bazės. Jei laikina bazė buvo nustatyta anksčiau, ji yra atšaukta. Rankiniu būdu atnaujinkite Pod statusą Omnipod skirtuke. Laikinos bazės trukmė turi būti didesnis už nulį %1$s min. kartotinis. - Gali būti, kad laikas nebuvo nustatytas. Insulino leidimas gali būti nutrauktas! Rankiniu būdu atnaujinkite Pod būseną Omnipod skirtuke ir, jei reikia, atnaujinkite insulino leidimą. - Nepavyko nustatyti laiko. Insulino leidimas sustabdytas! Rankiniu būdu atnaujinkite insulino leidimą iš Omnipod skirtuko. + Gali būti, kad laikas nebuvo nustatytas. Insulino tiekimas gali būti nutrauktas! Rankiniu būdu atnaujinkite Pod statusą Omnipod skirtuke ir, jei reikia, atnaujinkite insulino tiekimą. + Nepavyko nustatyti laiko. Insulino tiekimas sustabdytas! Rankiniu būdu atnaujinkite insulino tiekimą Omnipod skirtuke. Nepavyko perskaityti Pulse žurnalo Nepavyko automatiškai pakeisti Pod laiko. Jūs turėtumėte sinchronizuoti laiką rankiniu būdu Omnipod skirtuke. Operacija negalima.\n\nPirmiausia turite sukonfigūruoti Omnipod pompą prieš atliekant šią operaciją. @@ -57,16 +57,16 @@ Nepavyko patikrinti, ar SMB (%1$.2fvv) suleistas. Jei esate tikri, kad bolusas nebuvo suleistas, turite rankiniu būdu pašalinti SMB įrašą iš Terapijos. Pod veikia laikina bazė, bet AAPS neinformuota apie šią laikiną bazę. Atšaukite laikiną bazę rankiniu būdu. - RileyLink statusas - Laukiama RileyLink ryšio… + RileyLink statistika + Laukiama ryšio su RileyLink… Išvalyti RileyLink nustatymus Skaityti Pulse žurnalą Skaitomas Pulse žurnalas… Pulse žurnalas Pulse žurnalas (nukopijuotas į mainų sritį) - Užpildykite naują Pod insulinu, kurio pakaktų 3 dienoms.\n\nTaip pat atkreipkite dėmesį į du pyptelėjimus iš Pod, kai pildote. Tai rodo, kad rezervuaras buvo užpildytas mažiausiai 80vv. Visiškai ištuštinkite švirkštą, net jei girdėjote du pyptelėjimus.\n\nUžpildę Pod, spustelėkite Kitas.\n\nPastaba: vykdant šiuos veiksmus, nenuimkite Pod adatos apsauginio dangtelio.\nPastaba: padėkite RileyLink vertikalioje pozicijoje ir Pod kelių colių atstumu vienas nuo kito. - Pabandykite suporuoti naują Pod ir užpildykite jį.\n\nKai inicijavimo procesas bus užbaigtas sėkmingai, galite spustelėti Kitas.\n\n Pastaba: padėkite RileyLink vertikalioje pozicijoje ir Pod kelių cm atstumu vienas nuo kito. + Užpildykite naują Pod insulinu, kurio pakaktų 3 dienoms.\n\nTaip pat atkreipkite dėmesį į du pyptelėjimus iš Pod, kai pildote. Tai rodo, kad rezervuaras buvo užpildytas mažiausiai 80 v. Visiškai ištuštinkite švirkštą, net jei girdėjote du pyptelėjimus.\n\nUžpildę Pod, spustelėkite Kitas.\n\nPastaba: vykdant šiuos veiksmus, nenuimkite Pod adatos apsauginio dangtelio.\nPastaba: padėkite RileyLink vertikalioje pozicijoje ir Pod kelių cm atstumu vienas nuo kito. + Vyksta naujo Pod susiejimas ir užpildymas.\n\nSėkmingai užsibaigus inicijavimo procesui, spauskite Kitas.\n\n Pastaba: padėkite RileyLink vertikalioje pozicijoje ir Pod kelių cm atstumu vienas nuo kito. - neaiški + neaišku diff --git a/pump/pump-common/src/main/java/info/nightscout/pump/common/ui/PumpBLEConfigActivity.kt b/pump/pump-common/src/main/java/info/nightscout/pump/common/ui/PumpBLEConfigActivity.kt index 52ac11c8d1..68c6f30eed 100644 --- a/pump/pump-common/src/main/java/info/nightscout/pump/common/ui/PumpBLEConfigActivity.kt +++ b/pump/pump-common/src/main/java/info/nightscout/pump/common/ui/PumpBLEConfigActivity.kt @@ -20,7 +20,7 @@ import android.widget.AdapterView import android.widget.AdapterView.OnItemClickListener import android.widget.BaseAdapter import android.widget.TextView -import dagger.android.support.DaggerAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.ui.dialogs.OKDialog import info.nightscout.interfaces.plugin.ActivePlugin import info.nightscout.interfaces.pump.BlePreCheck @@ -37,7 +37,7 @@ import org.apache.commons.lang3.StringUtils import javax.inject.Inject @SuppressLint("MissingPermission") -class PumpBLEConfigActivity : DaggerAppCompatActivity() { +class PumpBLEConfigActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var sp: SP diff --git a/pump/pump-common/src/main/java/info/nightscout/pump/common/ui/PumpHistoryActivity.kt b/pump/pump-common/src/main/java/info/nightscout/pump/common/ui/PumpHistoryActivity.kt index b61693aba3..ab4f66d5cc 100644 --- a/pump/pump-common/src/main/java/info/nightscout/pump/common/ui/PumpHistoryActivity.kt +++ b/pump/pump-common/src/main/java/info/nightscout/pump/common/ui/PumpHistoryActivity.kt @@ -11,7 +11,7 @@ import android.widget.ArrayAdapter import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import dagger.android.support.DaggerAppCompatActivity +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.interfaces.plugin.ActivePlugin import info.nightscout.pump.common.R import info.nightscout.pump.common.databinding.PumpHistoryActivityBinding @@ -22,12 +22,10 @@ import info.nightscout.pump.common.driver.history.PumpHistoryEntry import info.nightscout.pump.common.driver.history.PumpHistoryText import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.LTag -import info.nightscout.shared.interfaces.ResourceHelper import javax.inject.Inject -class PumpHistoryActivity : DaggerAppCompatActivity() { +class PumpHistoryActivity : TranslatedDaggerAppCompatActivity() { - @Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var context: Context diff --git a/pump/pump-common/src/main/res/values-lt-rLT/strings.xml b/pump/pump-common/src/main/res/values-lt-rLT/strings.xml index b60de68e8c..5ad947fbf0 100644 --- a/pump/pump-common/src/main/res/values-lt-rLT/strings.xml +++ b/pump/pump-common/src/main/res/values-lt-rLT/strings.xml @@ -3,12 +3,12 @@ Pompa ir/ar jos valdiklis nepalaiko operacijos. Pompa dar nepalaiko operacijos. - Gerai + OK Pompos serijos Nr. %1$.2f v iš %2$.2f v suleista - Nepavyko paleisti - Inicijuojama + Neinicializuota + Inicializuota Ryšio šifravimas Pasirengusi Užimta diff --git a/pump/pump-core/src/main/res/values-lt-rLT/strings.xml b/pump/pump-core/src/main/res/values-lt-rLT/strings.xml index a86db15648..a4d9c7e9e3 100644 --- a/pump/pump-core/src/main/res/values-lt-rLT/strings.xml +++ b/pump/pump-core/src/main/res/values-lt-rLT/strings.xml @@ -2,7 +2,7 @@ Niekada nebuvo sujungta - Užmigusi + Miego režime Pažadinimas Aktyvi Ryšio klaida diff --git a/pump/rileylink/src/main/java/info/nightscout/androidaps/plugins/pump/common/dialog/RileyLinkBLEConfigActivity.kt b/pump/rileylink/src/main/java/info/nightscout/androidaps/plugins/pump/common/dialog/RileyLinkBLEConfigActivity.kt index ca61cee722..a9d0e976b3 100644 --- a/pump/rileylink/src/main/java/info/nightscout/androidaps/plugins/pump/common/dialog/RileyLinkBLEConfigActivity.kt +++ b/pump/rileylink/src/main/java/info/nightscout/androidaps/plugins/pump/common/dialog/RileyLinkBLEConfigActivity.kt @@ -26,13 +26,13 @@ import android.widget.BaseAdapter import android.widget.TextView import android.widget.Toast import androidx.core.app.ActivityCompat -import dagger.android.support.DaggerAppCompatActivity import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.R import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.databinding.RileyLinkBleConfigActivityBinding import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkPumpDevice +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.ui.dialogs.OKDialog import info.nightscout.interfaces.plugin.ActivePlugin import info.nightscout.interfaces.pump.BlePreCheck @@ -45,7 +45,7 @@ import java.util.Locale import javax.inject.Inject // IMPORTANT: This activity needs to be called from RileyLinkSelectPreference (see pref_medtronic.xml as example) -class RileyLinkBLEConfigActivity : DaggerAppCompatActivity() { +class RileyLinkBLEConfigActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var sp: SP @Inject lateinit var blePreCheck: BlePreCheck diff --git a/pump/rileylink/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusActivity.kt b/pump/rileylink/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusActivity.kt index 010a61f0ce..2fd7f8c177 100644 --- a/pump/rileylink/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusActivity.kt +++ b/pump/rileylink/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusActivity.kt @@ -5,13 +5,13 @@ import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.viewpager2.adapter.FragmentStateAdapter import com.google.android.material.tabs.TabLayoutMediator -import dagger.android.support.DaggerAppCompatActivity import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.R import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.databinding.RileylinkStatusBinding +import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.shared.interfaces.ResourceHelper import javax.inject.Inject -class RileyLinkStatusActivity : DaggerAppCompatActivity() { +class RileyLinkStatusActivity : TranslatedDaggerAppCompatActivity() { @Inject lateinit var rh: ResourceHelper diff --git a/pump/rileylink/src/main/res/values-lt-rLT/strings.xml b/pump/rileylink/src/main/res/values-lt-rLT/strings.xml index ea4d800cc7..52290bf439 100644 --- a/pump/rileylink/src/main/res/values-lt-rLT/strings.xml +++ b/pump/rileylink/src/main/res/values-lt-rLT/strings.xml @@ -16,7 +16,7 @@ Pašalinti RileyLink Nepasirinktas RileyLink - Parametrai + Nustatymai Istorija RileyLink statusas Pompos statusas @@ -26,7 +26,7 @@ Vardas: Baterijos lygis: %1$d%% - Ryšio būsena: + Ryšio statusas: Prisijungimo klaida: Įrenginys Įrenginio tipas: diff --git a/ui/src/main/AndroidManifest.xml b/ui/src/main/AndroidManifest.xml index 12f051996e..e8d94f6b6c 100644 --- a/ui/src/main/AndroidManifest.xml +++ b/ui/src/main/AndroidManifest.xml @@ -26,7 +26,8 @@ + android:exported="false" + android:theme="@style/AppTheme.NoActionBar" /> 3 часа 10 часа - правилен резултат с % коригирайте резултат с единици Резултат: %1$s %2$s diff --git a/ui/src/main/res/values-cs-rCZ/strings.xml b/ui/src/main/res/values-cs-rCZ/strings.xml index 8c7dee3eec..5195ee3115 100644 --- a/ui/src/main/res/values-cs-rCZ/strings.xml +++ b/ui/src/main/res/values-cs-rCZ/strings.xml @@ -119,7 +119,7 @@ Aktuální profil Dostupný profil Věk: %1$.0f CDD: %2$.0f U - Věk: %1$.0f CDD: %2$.0f U %3$d%% + Věk: %1$d CDD: %2$.0f U %3$d%% Věk: %1$.0f Hmotnost: %2$.0f kg Výchozí profil DPV Neplatný vstup % diff --git a/ui/src/main/res/values-sk-rSK/strings.xml b/ui/src/main/res/values-sk-rSK/strings.xml index ef5408b74d..949adc996d 100644 --- a/ui/src/main/res/values-sk-rSK/strings.xml +++ b/ui/src/main/res/values-sk-rSK/strings.xml @@ -119,7 +119,7 @@ Aktuálny profil Dostupný profil Vek: %1$.0f CDD: %2$.0f JI - Vek: %1$.0f CDD: %2$.0f JI %3$d%% + Vek: %1$d CDD: %2$.0f JI %3$d%% Vek: %1$.0f Hmotnosť: %2$.0f kg Predvolený DPV profil Neplatný vstup % diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml index 1b5db5dc26..2a08a7f623 100644 --- a/wear/src/main/AndroidManifest.xml +++ b/wear/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + @@ -268,6 +269,10 @@ + + Unit = { hr -> ctx.startService(IntentWearToMobile(ctx, hr)) } + + override fun isDisposed() = schedule == null + + override fun dispose() { + aapsLogger.info(LTag.WEAR, "Dispose ${javaClass.simpleName}") + schedule?.dispose() + (ctx.getSystemService(SENSOR_SERVICE) as SensorManager?)?.unregisterListener(this) + } + + /** Sends currently sampled value to the phone. Executed every [samplingIntervalMillis]. */ + private fun send() { + send(System.currentTimeMillis()) + } + + @VisibleForTesting + fun send(timestampMillis: Long) { + sampler.getAndReset(timestampMillis)?.let { hr -> + aapsLogger.info(LTag.WEAR, "Send heart rate $hr") + sendHeartRate(hr) + } + } + + override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) { + onAccuracyChanged(sensor.type, accuracy, System.currentTimeMillis()) + } + + @VisibleForTesting + fun onAccuracyChanged(sensorType: Int, accuracy: Int, timestampMillis: Long) { + if (sensorType != Sensor.TYPE_HEART_RATE) { + aapsLogger.error(LTag.WEAR, "Invalid SensorEvent $sensorType $accuracy") + return + } + if (accuracy !in goodAccuracies) sampler.setHeartRate(timestampMillis, null) + } + + override fun onSensorChanged(event: SensorEvent) { + onSensorChanged(event.sensor?.type, event.accuracy, System.currentTimeMillis(), event.values) + } + + @VisibleForTesting + fun onSensorChanged(sensorType: Int?, accuracy: Int, timestampMillis: Long, values: FloatArray) { + if (sensorType == null || sensorType != Sensor.TYPE_HEART_RATE || values.isEmpty()) { + aapsLogger.error(LTag.WEAR, "Invalid SensorEvent $sensorType $accuracy $timestampMillis ${values.joinToString()}") + return + } + val heartRate = values[0].toDouble().takeIf { accuracy in goodAccuracies } + sampler.setHeartRate(timestampMillis, heartRate) + } + + private class Sampler(timestampMillis: Long) { + private var startMillis: Long = timestampMillis + private var lastEventMillis: Long = timestampMillis + /** Number of heart beats sampled so far. */ + private var beats: Double = 0.0 + /** Time we could sample valid values during the current sampling interval. */ + private var activeMillis: Long = 0 + private val device = (Build.MANUFACTURER ?: "unknown") + " " + (Build.MODEL ?: "unknown") + private val lock = ReentrantLock() + + var currentBpm: Double? = null + get() = field + private set(value) { field = value } + + private fun Long.toMinute(): Double = this / 60_000.0 + + private fun fix(timestampMillis: Long) { + currentBpm?.let { bpm -> + val elapsed = timestampMillis - lastEventMillis + beats += elapsed.toMinute() * bpm + activeMillis += elapsed + } + lastEventMillis = timestampMillis + } + + /** Gets the current sampled value and resets the samplers clock to the given timestamp. */ + fun getAndReset(timestampMillis: Long): EventData.ActionHeartRate? { + lock.withLock { + fix(timestampMillis) + return if (10 * activeMillis > lastEventMillis - startMillis) { + val bpm = beats / activeMillis.toMinute() + EventData.ActionHeartRate(timestampMillis - startMillis, timestampMillis, bpm, device) + } else { + null + }.also { + startMillis = timestampMillis + lastEventMillis = timestampMillis + beats = 0.0 + activeMillis = 0 + } + } + } + + fun setHeartRate(timestampMillis: Long, heartRate: Double?) { + lock.withLock { + if (timestampMillis < lastEventMillis) return + fix(timestampMillis) + currentBpm = heartRate + } + } + } +} diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/ConfigurationActivity.kt b/wear/src/main/java/info/nightscout/androidaps/interaction/ConfigurationActivity.kt index 1aebe91433..d3475dc4df 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/ConfigurationActivity.kt +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/ConfigurationActivity.kt @@ -1,6 +1,5 @@ package info.nightscout.androidaps.interaction -import preference.WearPreferenceActivity import android.os.Bundle import android.view.View import android.view.ViewGroup @@ -9,7 +8,7 @@ import dagger.android.AndroidInjection import info.nightscout.androidaps.R import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.LTag - +import preference.WearPreferenceActivity import javax.inject.Inject class ConfigurationActivity : WearPreferenceActivity() { diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/WatchfaceConfigurationActivity.kt b/wear/src/main/java/info/nightscout/androidaps/interaction/WatchfaceConfigurationActivity.kt index 2b06023155..aaec70673b 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/WatchfaceConfigurationActivity.kt +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/WatchfaceConfigurationActivity.kt @@ -1,17 +1,28 @@ package info.nightscout.androidaps.interaction +import android.Manifest +import android.content.SharedPreferences +import android.content.pm.PackageManager import android.os.Bundle import android.view.View import android.view.ViewGroup +import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.preference.PreferenceManager import info.nightscout.androidaps.R +import info.nightscout.rx.logging.AAPSLogger +import info.nightscout.rx.logging.LTag import preference.WearPreferenceActivity +import javax.inject.Inject -class WatchfaceConfigurationActivity : WearPreferenceActivity() { +class WatchfaceConfigurationActivity : WearPreferenceActivity(), SharedPreferences.OnSharedPreferenceChangeListener { + + @Inject lateinit var aapsLogger: AAPSLogger override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) addPreferencesFromResource(R.xml.preferences) + PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this) val view = window.decorView as ViewGroup removeBackgroundRecursively(view) view.background = ContextCompat.getDrawable(this, R.drawable.settings_background) @@ -24,4 +35,38 @@ class WatchfaceConfigurationActivity : WearPreferenceActivity() { removeBackgroundRecursively(parent.getChildAt(i)) parent.background = null } + + override fun onSharedPreferenceChanged(sp: SharedPreferences, key: String?) { + if (key == getString(R.string.key_heart_rate_sampling)) { + if (sp.getBoolean(key, false)) { + requestBodySensorPermission() + } + } + } + + private fun requestBodySensorPermission() { + val permission = Manifest.permission.BODY_SENSORS + if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, arrayOf(permission), BODY_SENSOR_PERMISSION_REQUEST_CODE) + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray) { + if (requestCode == BODY_SENSOR_PERMISSION_REQUEST_CODE) { + if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) { + aapsLogger.info(LTag.WEAR, "Sensor permission for heart rate granted") + } else { + aapsLogger.warn(LTag.WEAR, "Sensor permission for heart rate denied") + } + } else { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + } + } + + companion object { + private const val BODY_SENSOR_PERMISSION_REQUEST_CODE = 1 + } } \ No newline at end of file diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/utils/BaseWatchFace.kt b/wear/src/main/java/info/nightscout/androidaps/watchfaces/utils/BaseWatchFace.kt index c31325b3cb..a1cc53eab9 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/utils/BaseWatchFace.kt +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/utils/BaseWatchFace.kt @@ -20,6 +20,7 @@ import dagger.android.AndroidInjection import info.nightscout.androidaps.R import info.nightscout.androidaps.data.RawDisplayData import info.nightscout.androidaps.events.EventWearPreferenceChange +import info.nightscout.androidaps.heartrate.HeartRateListener import info.nightscout.androidaps.interaction.menus.MainMenuActivity import info.nightscout.androidaps.interaction.utils.Persistence import info.nightscout.androidaps.interaction.utils.WearUtil @@ -99,6 +100,7 @@ abstract class BaseWatchFace : WatchFace() { private var mLastSvg = "" private var mLastDirection = "" + private var heartRateListener: HeartRateListener? = null override fun onCreate() { // Not derived from DaggerService, do injection here @@ -115,6 +117,7 @@ abstract class BaseWatchFace : WatchFace() { .subscribe { event: EventWearPreferenceChange -> simpleUi.updatePreferences() if (event.changedKey != null && event.changedKey == "delta_granularity") rxBus.send(EventWearToMobile(ActionResendData("BaseWatchFace:onSharedPreferenceChanged"))) + if (event.changedKey == getString(R.string.key_heart_rate_sampling)) updateHeartRateListener() if (layoutSet) setDataFields() invalidate() } @@ -139,6 +142,7 @@ abstract class BaseWatchFace : WatchFace() { layoutView = binding.root performViewSetup() rxBus.send(EventWearToMobile(ActionResendData("BaseWatchFace::onCreate"))) + updateHeartRateListener() } private fun forceUpdate() { @@ -146,6 +150,20 @@ abstract class BaseWatchFace : WatchFace() { invalidate() } + private fun updateHeartRateListener() { + if (sp.getBoolean(R.string.key_heart_rate_sampling, false)) { + if (heartRateListener == null) { + heartRateListener = HeartRateListener( + this, aapsLogger, aapsSchedulers).also { hrl -> disposable += hrl } + } + } else { + heartRateListener?.let { hrl -> + disposable.remove(hrl) + heartRateListener = null + } + } + } + override fun onTapCommand(tapType: Int, x: Int, y: Int, eventTime: Long) { binding.chart?.let { chart -> if (tapType == TAP_TYPE_TAP && x >= chart.left && x <= chart.right && y >= chart.top && y <= chart.bottom) { diff --git a/wear/src/main/res/values-lt-rLT/strings.xml b/wear/src/main/res/values-lt-rLT/strings.xml index 0f5e0fef83..926b5eee49 100644 --- a/wear/src/main/res/values-lt-rLT/strings.xml +++ b/wear/src/main/res/values-lt-rLT/strings.xml @@ -75,7 +75,7 @@ AV iAV Parametrai - Būsena + Statusas Suderinti dar kartą Užpildymas Nėra @@ -180,12 +180,12 @@ S: -- Prieš -- min. --- mg/dl - Ciklo būsena nežinoma + Ciklo statusas nežinomas 000g 00,0 0,00v - AAPS atidėti aliarmą - Atidėjimas siunčiamas AAPS + Nutildyti aliarmą AAPS + Nutildymas siunčiamas AAPS val sav d diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml index 08ecf60ce7..a9f53bd230 100644 --- a/wear/src/main/res/values/strings.xml +++ b/wear/src/main/res/values/strings.xml @@ -233,5 +233,6 @@ old !old! !err! - + heart_rate_sampling + Heart Rate diff --git a/wear/src/main/res/xml/preferences.xml b/wear/src/main/res/xml/preferences.xml index 5719c3ea66..3033bc862d 100644 --- a/wear/src/main/res/xml/preferences.xml +++ b/wear/src/main/res/xml/preferences.xml @@ -180,4 +180,11 @@ android:summary="Input Design" android:title="@string/pref_moreWatchfaceSettings" /> + diff --git a/wear/src/test/java/info/nightscout/androidaps/heartrate/HeartRateListenerTest.kt b/wear/src/test/java/info/nightscout/androidaps/heartrate/HeartRateListenerTest.kt new file mode 100644 index 0000000000..7b542b1afc --- /dev/null +++ b/wear/src/test/java/info/nightscout/androidaps/heartrate/HeartRateListenerTest.kt @@ -0,0 +1,164 @@ +package info.nightscout.androidaps.heartrate + +import android.content.Context +import android.hardware.Sensor +import android.hardware.SensorManager +import info.nightscout.rx.AapsSchedulers +import info.nightscout.rx.logging.AAPSLoggerTest +import info.nightscout.rx.weardata.EventData.ActionHeartRate +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.disposables.Disposable +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import java.util.concurrent.TimeUnit + +internal class HeartRateListenerTest { + private val aapsLogger = AAPSLoggerTest() + private val aapsSchedulers = object: AapsSchedulers { + override val main: Scheduler = mock(Scheduler::class.java) + override val io: Scheduler = mock(Scheduler::class.java) + override val cpu: Scheduler = mock(Scheduler::class.java) + override val newThread: Scheduler = mock(Scheduler::class.java) + } + private val schedule = mock(Disposable::class.java) + private val heartRates = mutableListOf() + private val device = "unknown unknown" + + private fun create(timestampMillis: Long): HeartRateListener { + val ctx = mock(Context::class.java) + `when`(aapsSchedulers.io.schedulePeriodicallyDirect( + any(), eq(60_000L), eq(60_000L), eq(TimeUnit.MILLISECONDS))).thenReturn(schedule) + val listener = HeartRateListener(ctx, aapsLogger, aapsSchedulers, timestampMillis) + verify(aapsSchedulers.io).schedulePeriodicallyDirect( + any(), eq(60_000L), eq(60_000L), eq(TimeUnit.MILLISECONDS)) + listener.sendHeartRate = { hr -> heartRates.add(hr) } + return listener + } + + private fun sendSensorEvent( + listener: HeartRateListener, + timestamp: Long, + heartRate: Int, + sensorType: Int? = Sensor.TYPE_HEART_RATE, + accuracy: Int = SensorManager.SENSOR_STATUS_ACCURACY_HIGH) { + listener.onSensorChanged(sensorType, accuracy, timestamp, floatArrayOf(heartRate.toFloat())) + } + + @BeforeEach + fun before() { + heartRates.clear() + } + + @AfterEach + fun cleanup() { + Mockito.verifyNoInteractions(aapsSchedulers.main) + Mockito.verifyNoMoreInteractions(aapsSchedulers.io) + Mockito.verifyNoInteractions(aapsSchedulers.cpu) + Mockito.verifyNoInteractions(aapsSchedulers.newThread) + verify(schedule).dispose() + } + + @Test + fun onSensorChanged() { + val start = System.currentTimeMillis() + val d1 = 10_000L + val d2 = 20_000L + val listener = create(start) + + assertNull(listener.currentHeartRateBpm) + sendSensorEvent(listener, start + d1, 80) + assertEquals(0, heartRates.size) + assertEquals(80, listener.currentHeartRateBpm) + + listener.send(start + d2) + assertEquals(1, heartRates.size) + assertEquals(ActionHeartRate(d2, start + d2, 80.0, device), heartRates.first()) + listener.dispose() + } + + @Test + fun onSensorChanged2() { + val start = System.currentTimeMillis() + val d1 = 10_000L + val d2 = 40_000L + val listener = create(start) + + sendSensorEvent(listener, start, 80) + assertEquals(0, heartRates.size) + assertEquals(80, listener.currentHeartRateBpm) + sendSensorEvent(listener, start + d1,100) + assertEquals(0, heartRates.size) + assertEquals(100, listener.currentHeartRateBpm) + + + listener.send(start + d2) + assertEquals(1, heartRates.size) + assertEquals(ActionHeartRate(d2, start + d2, 95.0, device), heartRates.first()) + listener.dispose() + } + + @Test + fun onSensorChangedMultiple() { + val start = System.currentTimeMillis() + val d1 = 10_000L + val d2 = 40_000L + val listener = create(start) + + sendSensorEvent(listener, start, 80) + listener.send(start + d1) + assertEquals(1, heartRates.size) + + sendSensorEvent(listener, start + d1,100) + assertEquals(1, heartRates.size) + listener.send(start + d2) + assertEquals(2, heartRates.size) + + assertEquals(ActionHeartRate(d1, start + d1, 80.0, device), heartRates[0]) + assertEquals(ActionHeartRate(d2 - d1, start + d2, 100.0, device), heartRates[1]) + listener.dispose() + } + + @Test + fun onSensorChangedNoContact() { + val start = System.currentTimeMillis() + val d1 = 10_000L + val d2 = 40_000L + val listener = create(start) + + sendSensorEvent(listener, start, 80) + sendSensorEvent(listener, start + d1, 100, accuracy = SensorManager.SENSOR_STATUS_NO_CONTACT) + assertNull(listener.currentHeartRateBpm) + listener.send(start + d2) + + assertEquals(1, heartRates.size) + assertEquals(ActionHeartRate(d2, start + d2, 80.0, device), heartRates.first()) + listener.dispose() + } + + @Test + fun onAccuracyChanged() { + val start = System.currentTimeMillis() + val d1 = 10_000L + val d2 = 40_000L + val d3 = 70_000L + val listener = create(start) + + sendSensorEvent(listener, start, 80) + listener.onAccuracyChanged(Sensor.TYPE_HEART_RATE, SensorManager.SENSOR_STATUS_UNRELIABLE, start + d1) + sendSensorEvent(listener, start + d2, 100) + listener.send(start + d3) + + assertEquals(1, heartRates.size) + assertEquals(ActionHeartRate(d3, start + d3, 95.0, device), heartRates.first()) + listener.dispose() + } +} diff --git a/workflow/src/main/java/info/nightscout/workflow/PrepareTreatmentsDataWorker.kt b/workflow/src/main/java/info/nightscout/workflow/PrepareTreatmentsDataWorker.kt index f012e8c4b9..f5e2a3e747 100644 --- a/workflow/src/main/java/info/nightscout/workflow/PrepareTreatmentsDataWorker.kt +++ b/workflow/src/main/java/info/nightscout/workflow/PrepareTreatmentsDataWorker.kt @@ -3,6 +3,7 @@ package info.nightscout.workflow import android.content.Context import androidx.work.WorkerParameters import androidx.work.workDataOf +import com.jjoe64.graphview.series.LineGraphSeries import info.nightscout.core.events.EventIobCalculationProgress import info.nightscout.core.graph.OverviewData import info.nightscout.core.graph.data.BolusDataPoint @@ -10,6 +11,8 @@ import info.nightscout.core.graph.data.CarbsDataPoint import info.nightscout.core.graph.data.DataPointWithLabelInterface import info.nightscout.core.graph.data.EffectiveProfileSwitchDataPoint import info.nightscout.core.graph.data.ExtendedBolusDataPoint +import info.nightscout.core.graph.data.FixedLineGraphSeries +import info.nightscout.core.graph.data.HeartRateDataPoint import info.nightscout.core.graph.data.PointsWithLabelGraphSeries import info.nightscout.core.graph.data.TherapyEventDataPoint import info.nightscout.core.utils.receivers.DataWorkerStorage @@ -129,6 +132,11 @@ class PrepareTreatmentsDataWorker( data.overviewData.therapyEventSeries = PointsWithLabelGraphSeries(filteredTherapyEvents.toTypedArray()) data.overviewData.epsSeries = PointsWithLabelGraphSeries(filteredEps.toTypedArray()) + data.overviewData.heartRateGraphSeries = LineGraphSeries( + repository.getHeartRatesFromTimeToTime(fromTime, endTime) + .map { hr -> HeartRateDataPoint(hr, rh) } + .toTypedArray()).apply { color = rh.gac(null, info.nightscout.core.ui.R.attr.heartRateColor) } + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_TREATMENTS_DATA, 100, null)) return Result.success() } @@ -149,4 +157,4 @@ class PrepareTreatmentsDataWorker( private fun List.filterTimeframe(fromTime: Long, endTime: Long): List = filter { it.x + it.duration >= fromTime && it.x <= endTime } -} \ No newline at end of file +}