diff --git a/auto/build.gradle b/auto/build.gradle index 8c8111cb4..452b5bb68 100644 --- a/auto/build.gradle +++ b/auto/build.gradle @@ -13,8 +13,8 @@ android { applicationId "de.michelinside.glucodataauto" minSdk rootProject.minSdk targetSdk rootProject.targetSdk - versionCode 1028 - versionName "1.2" + versionCode 1029 + versionName "1.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -37,6 +37,14 @@ android { minifyEnabled false resValue "string", "app_name", "GlucoDataAuto Debug" } + second { + applicationIdSuffix '.second' + versionNameSuffix '_SECOND' + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + resValue "string", "app_name", "GDA Second" + signingConfig signingConfigs.debug + } applicationVariants.all { // this method is use to rename your all apk weather // it may be signed or unsigned(debug apk) @@ -75,7 +83,7 @@ dependencies { implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'com.google.android.material:material:1.12.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.constraintlayout:constraintlayout:2.2.0' implementation 'com.joaomgcd:taskerpluginlibrary:0.4.10' implementation project(path: ':common') implementation "androidx.car.app:app:1.4.0" @@ -105,4 +113,14 @@ afterEvaluate { into rootProject.releasePath } assembleDevRelease.finalizedBy(copyAndroidDevApksPostBuild) -} \ No newline at end of file + + //noinspection ConfigurationAvoidance + def assembleSecond = tasks.getByPath(':auto:assembleSecond') + def copyAndroidSecondApksPostBuild = tasks.register('copyAndroidSecondApksPostBuild', Copy) { + dependsOn assembleSecond + from "${projectDir}/second" + include '**/*.apk' + into rootProject.releasePath + } + assembleSecond.finalizedBy(copyAndroidSecondApksPostBuild) +} diff --git a/auto/src/main/ic_launcher-second-white.png b/auto/src/main/ic_launcher-second-white.png new file mode 100644 index 000000000..982b88f5a Binary files /dev/null and b/auto/src/main/ic_launcher-second-white.png differ diff --git a/auto/src/main/ic_launcher-second.png b/auto/src/main/ic_launcher-second.png new file mode 100644 index 000000000..5afd10461 Binary files /dev/null and b/auto/src/main/ic_launcher-second.png differ diff --git a/auto/src/main/java/de/michelinside/glucodataauto/GlucoDataServiceAuto.kt b/auto/src/main/java/de/michelinside/glucodataauto/GlucoDataServiceAuto.kt index 03c0d0e6d..e1f15ccb3 100644 --- a/auto/src/main/java/de/michelinside/glucodataauto/GlucoDataServiceAuto.kt +++ b/auto/src/main/java/de/michelinside/glucodataauto/GlucoDataServiceAuto.kt @@ -54,6 +54,8 @@ class GlucoDataServiceAuto: Service(), SharedPreferences.OnSharedPreferenceChang private var dexcomReceiver: DexcomBroadcastReceiver? = null private var nsEmulatorReceiver: NsEmulatorReceiver? = null private var diaboxReceiver: DiaboxReceiver? = null + private var patient_name: String? = null + val patientName: String? get() = patient_name val connected: Boolean get() = car_connected || CarMediaBrowserService.active @@ -72,12 +74,18 @@ class GlucoDataServiceAuto: Service(), SharedPreferences.OnSharedPreferenceChang Log.v(LOG_ID, "migrateSettings called") GlucoDataService.migrateSettings(context) val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) - if(sharedPref.getBoolean(Constants.SHARED_PREF_NIGHTSCOUT_IOB_COB, true)) { + if(!sharedPref.contains(Constants.SHARED_PREF_NIGHTSCOUT_IOB_COB)) { with(sharedPref.edit()) { putBoolean(Constants.SHARED_PREF_NIGHTSCOUT_IOB_COB, false) apply() } } + if(Constants.IS_SECOND && !sharedPref.contains(Constants.PATIENT_NAME)) { + with(sharedPref.edit()) { + putString(Constants.PATIENT_NAME, "SECOND") + apply() + } + } } private fun startService(context: Context, foreground: Boolean) { @@ -201,8 +209,9 @@ class GlucoDataServiceAuto: Service(), SharedPreferences.OnSharedPreferenceChang private fun sendStateBroadcast(context: Context, enabled: Boolean) { try { - Log.d(LOG_ID, "Sending state broadcast for state: " + enabled) + Log.i(LOG_ID, "Sending state broadcast for state: $enabled - to ${Constants.PACKAGE_GLUCODATAHANDLER}") val intent = Intent(Constants.GLUCODATAAUTO_STATE_ACTION) + intent.setPackage(Constants.PACKAGE_GLUCODATAHANDLER) intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) intent.putExtra(Constants.GLUCODATAAUTO_STATE_EXTRA, enabled) context.sendBroadcast(intent) @@ -346,6 +355,7 @@ class GlucoDataServiceAuto: Service(), SharedPreferences.OnSharedPreferenceChang TextToSpeechUtils.initTextToSpeech(this) val sharedPref = getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) sharedPref.registerOnSharedPreferenceChangeListener(this) + patient_name = sharedPref.getString(Constants.PATIENT_NAME, "") val isForeground = (if(intent != null) intent.getBooleanExtra(Constants.SHARED_PREF_FOREGROUND_SERVICE, false) else false) || sharedPref.getBoolean(Constants.SHARED_PREF_FOREGROUND_SERVICE, false) if (isForeground && !isForegroundService) { Log.i(LOG_ID, "Starting service in foreground!") @@ -399,7 +409,7 @@ class GlucoDataServiceAuto: Service(), SharedPreferences.OnSharedPreferenceChang .build() } - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { try { Log.d(LOG_ID, "onSharedPreferenceChanged called with key $key") when(key) { @@ -412,6 +422,9 @@ class GlucoDataServiceAuto: Service(), SharedPreferences.OnSharedPreferenceChang if(dataSyncCount>0) updateSourceReceiver(this, key) } + Constants.PATIENT_NAME -> { + patient_name = sharedPreferences.getString(Constants.PATIENT_NAME, "") + } } } catch (exc: Exception) { Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.toString()) diff --git a/auto/src/main/java/de/michelinside/glucodataauto/MainActivity.kt b/auto/src/main/java/de/michelinside/glucodataauto/MainActivity.kt index d08059380..17237aa96 100644 --- a/auto/src/main/java/de/michelinside/glucodataauto/MainActivity.kt +++ b/auto/src/main/java/de/michelinside/glucodataauto/MainActivity.kt @@ -1,7 +1,7 @@ package de.michelinside.glucodataauto +import android.annotation.SuppressLint import android.app.Activity -import android.app.AlarmManager import android.content.Context import android.content.Intent import android.content.SharedPreferences @@ -46,8 +46,6 @@ import de.michelinside.glucodatahandler.common.utils.GitHubVersionChecker import de.michelinside.glucodatahandler.common.utils.Utils import de.michelinside.glucodatahandler.common.ui.Dialogs import de.michelinside.glucodatahandler.common.utils.TextToSpeechUtils -import java.text.DateFormat -import java.util.Date import de.michelinside.glucodatahandler.common.R as CR class MainActivity : AppCompatActivity(), NotifierInterface { @@ -55,6 +53,8 @@ class MainActivity : AppCompatActivity(), NotifierInterface { private lateinit var viewIcon: ImageView private lateinit var timeText: TextView private lateinit var deltaText: TextView + private lateinit var iobText: TextView + private lateinit var cobText: TextView private lateinit var txtLastValue: TextView private lateinit var txtVersion: TextView private lateinit var tableDetails: TableLayout @@ -79,6 +79,8 @@ class MainActivity : AppCompatActivity(), NotifierInterface { viewIcon = findViewById(R.id.viewIcon) timeText = findViewById(R.id.timeText) deltaText = findViewById(R.id.deltaText) + iobText = findViewById(R.id.iobText) + cobText = findViewById(R.id.cobText) txtLastValue = findViewById(R.id.txtLastValue) btnSources = findViewById(R.id.btnSources) tableConnections = findViewById(R.id.tableConnections) @@ -165,6 +167,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface { GlucoDataServiceAuto.startDataSync() versionChecker.checkVersion(1) + checkNewSettings() } catch (exc: Exception) { Log.e(LOG_ID, "onResume exception: " + exc.message.toString() ) } @@ -178,27 +181,12 @@ class MainActivity : AppCompatActivity(), NotifierInterface { return false } } - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val alarmManager = this.getSystemService(Context.ALARM_SERVICE) as AlarmManager - if (!alarmManager.canScheduleExactAlarms()) { - Log.i(LOG_ID, "Request exact alarm permission...") - startActivity(Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)) - } - } requestExactAlarmPermission() return true } - private fun canScheduleExactAlarms(): Boolean { - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val alarmManager = this.getSystemService(Context.ALARM_SERVICE) as AlarmManager - return alarmManager.canScheduleExactAlarms() - } - return true - } - private fun requestExactAlarmPermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !canScheduleExactAlarms()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !Utils.canScheduleExactAlarms(this)) { Log.i(LOG_ID, "Request exact alarm permission...") val builder: AlertDialog.Builder = AlertDialog.Builder(this) builder @@ -215,6 +203,49 @@ class MainActivity : AppCompatActivity(), NotifierInterface { } } + private fun checkNewSettings() { + try { + if(!sharedPref.contains(Constants.SHARED_PREF_DISCLAIMER_SHOWN)) { + Dialogs.showOkDialog(this, + CR.string.gdh_disclaimer_title, + CR.string.gdh_disclaimer_message, + null + ) + with(sharedPref.edit()) { + putString(Constants.SHARED_PREF_DISCLAIMER_SHOWN, BuildConfig.VERSION_NAME) + apply() + } + } + if(!sharedPref.contains(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU)) { + if(sharedPref.getBoolean(Constants.SHARED_PREF_LIBRE_ENABLED, false)) { + Dialogs.showOkCancelDialog(this, + resources.getString(CR.string.src_cat_libreview), + resources.getString(CR.string.src_libre_tou_message), + { _, _ -> + with(sharedPref.edit()) { + putBoolean(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU, true) + apply() + } + }, + { _, _ -> + with(sharedPref.edit()) { + putBoolean(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU, false) + apply() + } + }) + } else { + with(sharedPref.edit()) { + putBoolean(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU, true) + apply() + } + } + } + + } catch (exc: Exception) { + Log.e(LOG_ID, "checkNewSettings exception: " + exc.message.toString() ) + } + } + override fun onCreateOptionsMenu(menu: Menu?): Boolean { try { Log.v(LOG_ID, "onCreateOptionsMenu called") @@ -311,6 +342,14 @@ class MainActivity : AppCompatActivity(), NotifierInterface { startActivity(mailIntent) return true } + R.id.action_google_groups -> { + val browserIntent = Intent( + Intent.ACTION_VIEW, + Uri.parse(resources.getText(CR.string.google_gdh_group_url).toString()) + ) + startActivity(browserIntent) + return true + } R.id.action_facebook -> { val browserIntent = Intent( Intent.ACTION_VIEW, @@ -355,6 +394,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface { return super.onOptionsItemSelected(item) } + @SuppressLint("SetTextI18n") private fun update() { try { Log.v(LOG_ID, "update values") @@ -370,6 +410,12 @@ class MainActivity : AppCompatActivity(), NotifierInterface { timeText.text = "🕒 ${ReceiveData.getElapsedRelativeTimeAsString(this)}" timeText.contentDescription = ReceiveData.getElapsedRelativeTimeAsString(this, true) deltaText.text = "Δ ${ReceiveData.getDeltaAsString()}" + iobText.text = "💉 " + ReceiveData.getIobAsString() + iobText.contentDescription = getString(CR.string.info_label_iob) + " " + ReceiveData.getIobAsString() + iobText.visibility = if (ReceiveData.isIobCobObsolete()) View.GONE else View.VISIBLE + cobText.text = "🍔 " + ReceiveData.getCobAsString() + cobText.contentDescription = getString(CR.string.info_label_cob) + " " + ReceiveData.getCobAsString() + cobText.visibility = iobText.visibility if(ReceiveData.time == 0L) { txtLastValue.visibility = View.VISIBLE @@ -394,17 +440,41 @@ class MainActivity : AppCompatActivity(), NotifierInterface { private fun updateConnectionsTable() { tableConnections.removeViews(1, maxOf(0, tableConnections.childCount - 1)) - if (SourceStateData.lastState != SourceState.NONE) - tableConnections.addView(createRow( - SourceStateData.lastSource.resId, - SourceStateData.getStateMessage(this))) + if (SourceStateData.lastState != SourceState.NONE) { + val msg = SourceStateData.getStateMessage(this) + tableConnections.addView(createRow(SourceStateData.lastSource.resId,msg)) + if(SourceStateData.lastState == SourceState.ERROR && SourceStateData.lastSource == DataSource.DEXCOM_SHARE) { + if (msg.contains("500:")) { // invalid password + val us_account = sharedPref.getBoolean(Constants.SHARED_PREF_DEXCOM_SHARE_USE_US_URL, false) + val browserIntent = Intent( + Intent.ACTION_VIEW, + Uri.parse(resources.getString(if(us_account)CR.string.dexcom_account_us_url else CR.string.dexcom_account_non_us_url)) + ) + val onClickListener = View.OnClickListener { + startActivity(browserIntent) + } + tableConnections.addView( + createRow( + SourceStateData.lastSource.resId, + resources.getString(if(us_account) CR.string.dexcom_share_check_us_account else CR.string.dexcom_share_check_non_us_account), + onClickListener + ) + ) + } + } + if(SourceStateData.lastErrorInfo.isNotEmpty()) { + // add error specific information in an own row + tableConnections.addView(createRow(SourceStateData.lastErrorInfo)) + } + tableConnections.addView(createRow(CR.string.request_timestamp, Utils.getUiTimeStamp(SourceStateData.lastStateTime))) + } tableConnections.addView(createRow(CR.string.pref_cat_android_auto, if (GlucoDataServiceAuto.connected) resources.getString(CR.string.connected_label) else resources.getString(CR.string.disconnected_label))) checkTableVisibility(tableConnections) } private fun updateNotesTable() { tableNotes.removeViews(1, maxOf(0, tableNotes.childCount - 1)) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !canScheduleExactAlarms()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !Utils.canScheduleExactAlarms(this)) { Log.w(LOG_ID, "Schedule exact alarm is not active!!!") val onClickListener = View.OnClickListener { startActivity(Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)) @@ -469,16 +539,20 @@ class MainActivity : AppCompatActivity(), NotifierInterface { tableAlarms.addView(createRow(CR.string.info_label_alarm, resources.getString(deltaAlarmType.resId))) } if (AlarmHandler.isSnoozeActive) - tableAlarms.addView(createRow(CR.string.snooze, AlarmHandler.snoozeTimestamp)) + tableAlarms.addView(createRow(CR.string.snooze_until, AlarmHandler.snoozeTimestamp)) checkTableVisibility(tableAlarms) } private fun updateDetailsTable() { tableDetails.removeViews(1, maxOf(0, tableDetails.childCount - 1)) + if(!GlucoDataServiceAuto.patientName.isNullOrEmpty()) { + tableDetails.addView(createRow(CR.string.patient_name, GlucoDataServiceAuto.patientName!!)) + } + if(ReceiveData.time > 0) { if (ReceiveData.isMmol) tableDetails.addView(createRow(CR.string.info_label_raw, "${ReceiveData.rawValue} mg/dl")) - tableDetails.addView(createRow(CR.string.info_label_timestamp, DateFormat.getTimeInstance(DateFormat.DEFAULT).format(Date(ReceiveData.time)))) + tableDetails.addView(createRow(CR.string.info_label_timestamp, Utils.getUiTimeStamp(ReceiveData.time))) if (ReceiveData.sensorID?.isNotEmpty() == true) { tableDetails.addView(createRow(CR.string.info_label_sensor_id, if(BuildConfig.DEBUG) "ABCDE12345" else ReceiveData.sensorID!!)) } @@ -520,12 +594,12 @@ class MainActivity : AppCompatActivity(), NotifierInterface { return row } - private fun createRow(key: String, onClickListener: View.OnClickListener? = null) : TableRow { + private fun createRow(value: String, onClickListener: View.OnClickListener? = null) : TableRow { val row = TableRow(this) row.weightSum = 1f //row.setBackgroundColor(resources.getColor(R.color.table_row)) row.setPadding(Utils.dpToPx(5F, this)) - row.addView(createColumn(key, false, onClickListener)) + row.addView(createColumn(value, false, onClickListener)) return row } diff --git a/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarMediaBrowserService.kt b/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarMediaBrowserService.kt index 0a1d76ac8..1316f5a57 100644 --- a/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarMediaBrowserService.kt +++ b/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarMediaBrowserService.kt @@ -196,7 +196,8 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh Constants.SHARED_PREF_CAR_NOTIFICATION, Constants.AA_MEDIA_PLAYER_SPEAK_NEW_VALUE, Constants.SHARED_PREF_CAR_MEDIA, - Constants.SHARED_PREF_CAR_MEDIA_ICON_STYLE -> { + Constants.AA_MEDIA_ICON_STYLE, + Constants.AA_MEDIA_SHOW_IOB_COB -> { notifyChildrenChanged(MEDIA_ROOT_ID) } Constants.AA_MEDIA_PLAYER_SPEAK_VALUES -> { @@ -232,7 +233,7 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh } private fun getIcon(size: Int = 100): Bitmap? { - return when(sharedPref.getString(Constants.SHARED_PREF_CAR_MEDIA_ICON_STYLE, Constants.AA_MEDIA_ICON_STYLE_GLUCOSE_TREND)) { + return when(sharedPref.getString(Constants.AA_MEDIA_ICON_STYLE, Constants.AA_MEDIA_ICON_STYLE_GLUCOSE_TREND)) { Constants.AA_MEDIA_ICON_STYLE_TREND -> { BitmapUtils.getRateAsBitmap(width = size, height = size) } @@ -277,15 +278,31 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh private fun setGlucose() { if (sharedPref.getBoolean(Constants.SHARED_PREF_CAR_MEDIA,true)) { Log.i(LOG_ID, "setGlucose called") + var title = ReceiveData.getGlucoseAsString() + " (Δ " + ReceiveData.getDeltaAsString() + ")" + if (sharedPref.getBoolean(Constants.AA_MEDIA_SHOW_IOB_COB, false) && !ReceiveData.isIobCobObsolete()) { + title += "\n" + if(!ReceiveData.iob.isNaN()) { + title += "💉 " + ReceiveData.getIobAsString(true) + " " + } + if(!ReceiveData.cob.isNaN()) { + title += "🍔 " + ReceiveData.getCobAsString(true) + } + title = title.trim() + } + var subtitle = "" + if(!GlucoDataServiceAuto.patientName.isNullOrEmpty()) + subtitle += GlucoDataServiceAuto.patientName + " - " + subtitle += "🕒 " + ReceiveData.getElapsedTimeMinuteAsString(this) + session.setMetadata( MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, - ReceiveData.getGlucoseAsString() + " (Δ " + ReceiveData.getDeltaAsString() + ")" + title ) .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, - ReceiveData.getElapsedTimeMinuteAsString(this) + subtitle ) .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, getDuration()) .putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, getIcon(400)!!) diff --git a/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarMediaPlayer.kt b/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarMediaPlayer.kt index 9b5af1aca..b8bdb8219 100644 --- a/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarMediaPlayer.kt +++ b/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarMediaPlayer.kt @@ -9,6 +9,7 @@ import android.media.MediaPlayer import android.net.Uri import android.os.Bundle import android.util.Log +import de.michelinside.glucodataauto.GlucoDataServiceAuto import de.michelinside.glucodatahandler.common.Constants import de.michelinside.glucodatahandler.common.R import de.michelinside.glucodatahandler.common.ReceiveData @@ -150,7 +151,11 @@ object CarMediaPlayer: NotifierInterface { var uri: String? = null var requestAudioFocus = false if(!playSilent) { - file = TextToSpeechUtils.getAsFile(ReceiveData.getAsText(context, false, false), context) + var text = ReceiveData.getAsText(context, true, false) + if(!GlucoDataServiceAuto.patientName.isNullOrEmpty()) { + text = "${GlucoDataServiceAuto.patientName}, $text" + } + file = TextToSpeechUtils.getAsFile(text, context) if(file != null) { uri = file!!.absolutePath last_speak_time = ReceiveData.time diff --git a/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarNotification.kt b/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarNotification.kt index 6ac95067d..508c9e240 100644 --- a/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarNotification.kt +++ b/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarNotification.kt @@ -44,6 +44,7 @@ object CarNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceC private var last_notification_time = 0L const val FORCE_NEXT_NOTIFY = "force_next_notify" private var forceNextNotify = false + private var show_iob_cob = false @SuppressLint("StaticFieldLeak") private lateinit var notificationCompat: NotificationCompat.Builder @@ -91,7 +92,8 @@ object CarNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceC notification_interval = if (alarmOnly) -1 else sharedPref.getInt(Constants.SHARED_PREF_CAR_NOTIFICATION_INTERVAL_NUM, 1).toLong() val reappear_active = notification_reappear_interval > 0 notification_reappear_interval = if (alarmOnly) 0L else sharedPref.getInt(Constants.SHARED_PREF_CAR_NOTIFICATION_REAPPEAR_INTERVAL, 5).toLong() - Log.i(LOG_ID, "notification settings changed: active: " + enable_notification + " - interval: " + notification_interval + " - reappear:" + notification_reappear_interval) + show_iob_cob = sharedPref.getBoolean(Constants.SHARED_PREF_CAR_NOTIFICATION_SHOW_IOB_COB, show_iob_cob) + Log.i(LOG_ID, "notification settings changed: active: " + enable_notification + " - interval: " + notification_interval + " - reappear:" + notification_reappear_interval + " - show_iob_cob: " + show_iob_cob) if(init && GlucoDataServiceAuto.connected) { if (enable_notification) ElapsedTimeTask.setInterval(notification_reappear_interval) @@ -288,12 +290,29 @@ object CarNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceC .setImportant(true) .build() val messagingStyle = NotificationCompat.MessagingStyle(person) + var title = "" if (isObsolete) - messagingStyle.conversationTitle = context.getString(CR.string.no_new_value, ReceiveData.getElapsedTimeMinute()) + title = context.getString(CR.string.no_new_value, ReceiveData.getElapsedTimeMinute()) else - messagingStyle.conversationTitle = getAlarmText(context) + ReceiveData.getGlucoseAsString() + " (Δ " + ReceiveData.getDeltaAsString() + ")" + title = getAlarmText(context) + ReceiveData.getGlucoseAsString() + " (Δ " + ReceiveData.getDeltaAsString() + + if (show_iob_cob && !ReceiveData.isIobCobObsolete()) { + if(!ReceiveData.iob.isNaN()) { + title += " 💉 " + ReceiveData.getIobAsString(true) + } + if(!ReceiveData.cob.isNaN()) { + title += " 🍔 " + ReceiveData.getCobAsString(true) + } + } + title += ")" + messagingStyle.conversationTitle = title messagingStyle.isGroupConversation = false - messagingStyle.addMessage(DateFormat.getTimeInstance(DateFormat.SHORT).format(Date(ReceiveData.time)), System.currentTimeMillis(), person) + var message = "" + if(!GlucoDataServiceAuto.patientName.isNullOrEmpty()) { + message += GlucoDataServiceAuto.patientName + " - " + } + message += "🕒 " + DateFormat.getTimeInstance(DateFormat.SHORT).format(Date(ReceiveData.time)) + messagingStyle.addMessage(message, System.currentTimeMillis(), person) return messagingStyle } @@ -341,7 +360,8 @@ object CarNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceC Constants.SHARED_PREF_CAR_NOTIFICATION, Constants.SHARED_PREF_CAR_NOTIFICATION_ALARM_ONLY, Constants.SHARED_PREF_CAR_NOTIFICATION_INTERVAL_NUM, - Constants.SHARED_PREF_CAR_NOTIFICATION_REAPPEAR_INTERVAL -> { + Constants.SHARED_PREF_CAR_NOTIFICATION_REAPPEAR_INTERVAL, + Constants.SHARED_PREF_CAR_NOTIFICATION_SHOW_IOB_COB -> { updateSettings(sharedPreferences!!) } } diff --git a/auto/src/main/java/de/michelinside/glucodataauto/preferences/AlarmFragment.kt b/auto/src/main/java/de/michelinside/glucodataauto/preferences/AlarmFragment.kt index d6d875c12..b921ee5d2 100644 --- a/auto/src/main/java/de/michelinside/glucodataauto/preferences/AlarmFragment.kt +++ b/auto/src/main/java/de/michelinside/glucodataauto/preferences/AlarmFragment.kt @@ -14,6 +14,7 @@ import de.michelinside.glucodatahandler.common.notification.AlarmHandler import de.michelinside.glucodatahandler.common.notification.AlarmType import de.michelinside.glucodatahandler.common.notifier.InternalNotifier import de.michelinside.glucodatahandler.common.notifier.NotifySource +import de.michelinside.glucodatahandler.common.preferences.PreferenceHelper import de.michelinside.glucodatahandler.common.utils.GlucoDataUtils import de.michelinside.glucodatahandler.common.utils.Utils @@ -29,6 +30,7 @@ class AlarmFragment : PreferenceFragmentCompat() { settingsChanged = false preferenceManager.sharedPreferencesName = Constants.SHARED_PREF_TAG setPreferencesFromResource(R.xml.alarms, rootKey) + PreferenceHelper.replaceSecondSummary(findPreference("gda_info")) } catch (exc: Exception) { Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString()) } @@ -51,8 +53,8 @@ class AlarmFragment : PreferenceFragmentCompat() { override fun onResume() { Log.d(LOG_ID, "onResume called") try { - update() super.onResume() + update() } catch (exc: Exception) { Log.e(LOG_ID, "onResume exception: " + exc.toString()) } diff --git a/auto/src/main/java/de/michelinside/glucodataauto/preferences/GDANotificationSettingsFragment.kt b/auto/src/main/java/de/michelinside/glucodataauto/preferences/GDANotificationSettingsFragment.kt index 297703aa0..3a6c20c3c 100644 --- a/auto/src/main/java/de/michelinside/glucodataauto/preferences/GDANotificationSettingsFragment.kt +++ b/auto/src/main/java/de/michelinside/glucodataauto/preferences/GDANotificationSettingsFragment.kt @@ -34,6 +34,7 @@ class GDANotificationSettingsFragment: SettingsFragmentBase(R.xml.pref_gda_notif setEnableState(sharedPreferences, Constants.SHARED_PREF_CAR_NOTIFICATION_ALARM_ONLY, Constants.SHARED_PREF_CAR_NOTIFICATION) setEnableState(sharedPreferences, Constants.SHARED_PREF_CAR_NOTIFICATION_INTERVAL_NUM, Constants.SHARED_PREF_CAR_NOTIFICATION, Constants.SHARED_PREF_CAR_NOTIFICATION_ALARM_ONLY) setEnableState(sharedPreferences, Constants.SHARED_PREF_CAR_NOTIFICATION_REAPPEAR_INTERVAL, Constants.SHARED_PREF_CAR_NOTIFICATION, Constants.SHARED_PREF_CAR_NOTIFICATION_ALARM_ONLY) + setEnableState(sharedPreferences, Constants.SHARED_PREF_CAR_NOTIFICATION_SHOW_IOB_COB, Constants.SHARED_PREF_CAR_NOTIFICATION) } catch (exc: Exception) { Log.e(LOG_ID, "updateEnableStates exception: " + exc.toString()) } diff --git a/auto/src/main/java/de/michelinside/glucodataauto/preferences/GDASpeakSettingsFragment.kt b/auto/src/main/java/de/michelinside/glucodataauto/preferences/GDASpeakSettingsFragment.kt index 8def4f05c..5b308e5ca 100644 --- a/auto/src/main/java/de/michelinside/glucodataauto/preferences/GDASpeakSettingsFragment.kt +++ b/auto/src/main/java/de/michelinside/glucodataauto/preferences/GDASpeakSettingsFragment.kt @@ -1,5 +1,6 @@ package de.michelinside.glucodataauto.preferences +import android.content.Context import android.content.SharedPreferences import android.util.Log import androidx.core.content.ContextCompat @@ -23,7 +24,11 @@ class GDASpeakSettingsFragment: SettingsFragmentBase(R.xml.pref_gda_speak) { if(GlucoDataServiceAuto.connected) { CarMediaPlayer.play(requireContext()) } else { - TextToSpeechUtils.speak(ReceiveData.getAsText(requireContext(), false, false)) + var text = ReceiveData.getAsText(requireContext(), true, false) + if(!GlucoDataServiceAuto.patientName.isNullOrEmpty()) { + text = "${GlucoDataServiceAuto.patientName}, $text" + } + TextToSpeechUtils.speak(text) } true } diff --git a/auto/src/main/java/de/michelinside/glucodataauto/preferences/SettingsFragment.kt b/auto/src/main/java/de/michelinside/glucodataauto/preferences/SettingsFragment.kt index 84f966329..2077042ac 100644 --- a/auto/src/main/java/de/michelinside/glucodataauto/preferences/SettingsFragment.kt +++ b/auto/src/main/java/de/michelinside/glucodataauto/preferences/SettingsFragment.kt @@ -8,6 +8,7 @@ import de.michelinside.glucodataauto.BuildConfig import de.michelinside.glucodataauto.GlucoDataServiceAuto import de.michelinside.glucodataauto.R import de.michelinside.glucodatahandler.common.Constants +import de.michelinside.glucodatahandler.common.preferences.PreferenceHelper class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { @@ -19,6 +20,8 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP preferenceManager.sharedPreferencesName = Constants.SHARED_PREF_TAG setPreferencesFromResource(R.xml.preferences, rootKey) + PreferenceHelper.replaceSecondSummary(findPreference("gda_info")) + if (BuildConfig.DEBUG) { val notifySwitch = findPreference(Constants.SHARED_PREF_DUMMY_VALUES) diff --git a/auto/src/main/java/de/michelinside/glucodataauto/preferences/SourceOfflineFragment.kt b/auto/src/main/java/de/michelinside/glucodataauto/preferences/SourceOfflineFragment.kt index a3b5f4964..9e0383ba2 100644 --- a/auto/src/main/java/de/michelinside/glucodataauto/preferences/SourceOfflineFragment.kt +++ b/auto/src/main/java/de/michelinside/glucodataauto/preferences/SourceOfflineFragment.kt @@ -8,6 +8,7 @@ import androidx.preference.* import de.michelinside.glucodataauto.R import de.michelinside.glucodatahandler.common.R as CR import de.michelinside.glucodatahandler.common.Constants +import de.michelinside.glucodatahandler.common.preferences.PreferenceHelper class SourceOfflineFragment : PreferenceFragmentCompat() { @@ -21,18 +22,11 @@ class SourceOfflineFragment : PreferenceFragmentCompat() { preferenceManager.sharedPreferencesName = Constants.SHARED_PREF_TAG setPreferencesFromResource(R.xml.sources_offline, rootKey) - val prefEselLink = findPreference(Constants.SHARED_PREF_EVERSENSE_ESEL_INFO) - if(prefEselLink != null) { - prefEselLink.setOnPreferenceClickListener { - val browserIntent = Intent( - Intent.ACTION_VIEW, - Uri.parse(resources.getText(CR.string.esel_link).toString()) - ) - startActivity(browserIntent) - true - } - } + PreferenceHelper.replaceSecondTitle(findPreference("cat_gdh_info")) + PreferenceHelper.replaceSecondSummary(findPreference("source_gdh_info")) + PreferenceHelper.setLinkOnClick(findPreference(Constants.SHARED_PREF_EVERSENSE_ESEL_INFO), CR.string.esel_link, requireContext()) + PreferenceHelper.setLinkOnClick(findPreference("source_juggluco_video"), CR.string.video_tutorial_juggluco, requireContext()) } catch (exc: Exception) { Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString()) } diff --git a/auto/src/main/java/de/michelinside/glucodataauto/preferences/SourceOnlineFragment.kt b/auto/src/main/java/de/michelinside/glucodataauto/preferences/SourceOnlineFragment.kt deleted file mode 100644 index 7177efaf9..000000000 --- a/auto/src/main/java/de/michelinside/glucodataauto/preferences/SourceOnlineFragment.kt +++ /dev/null @@ -1,198 +0,0 @@ -package de.michelinside.glucodataauto.preferences - -import android.content.Context -import android.content.SharedPreferences -import android.os.Bundle -import android.text.InputType -import android.util.Log -import androidx.preference.* -import de.michelinside.glucodataauto.R -import de.michelinside.glucodatahandler.common.R as CR -import de.michelinside.glucodatahandler.common.Constants -import de.michelinside.glucodatahandler.common.notifier.InternalNotifier -import de.michelinside.glucodatahandler.common.notifier.NotifierInterface -import de.michelinside.glucodatahandler.common.notifier.NotifySource -import de.michelinside.glucodatahandler.common.tasks.DataSourceTask -import de.michelinside.glucodatahandler.common.tasks.LibreLinkSourceTask - - -class SourceOnlineFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener, NotifierInterface { - private val LOG_ID = "GDH.AA.SourceOnlineFragment" - private var settingsChanged = false - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - Log.d(LOG_ID, "onCreatePreferences called") - try { - settingsChanged = false - preferenceManager.sharedPreferencesName = Constants.SHARED_PREF_TAG - setPreferencesFromResource(R.xml.sources_online, rootKey) - - setPasswordPref(Constants.SHARED_PREF_LIBRE_PASSWORD) - setPasswordPref(Constants.SHARED_PREF_DEXCOM_SHARE_PASSWORD) - setPasswordPref(Constants.SHARED_PREF_NIGHTSCOUT_SECRET) - - setupLibrePatientData() - } catch (exc: Exception) { - Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString()) - } - } - - private fun setPasswordPref(prefName: String) { - val pwdPref = findPreference(prefName) - pwdPref?.setOnBindEditTextListener {editText -> - editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD - } - } - - override fun onDestroyView() { - Log.d(LOG_ID, "onDestroyView called") - try { - if (settingsChanged) { - InternalNotifier.notify(requireContext(), NotifySource.SOURCE_SETTINGS, DataSourceTask.getSettingsBundle(preferenceManager.sharedPreferences!!)) - } - } catch (exc: Exception) { - Log.e(LOG_ID, "onDestroyView exception: " + exc.toString()) - } - super.onDestroyView() - } - - - override fun onResume() { - Log.d(LOG_ID, "onResume called") - try { - preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) - updateEnableStates(preferenceManager.sharedPreferences!!) - InternalNotifier.addNotifier(requireContext(), this, mutableSetOf(NotifySource.PATIENT_DATA_CHANGED)) - update() - super.onResume() - } catch (exc: Exception) { - Log.e(LOG_ID, "onResume exception: " + exc.toString()) - } - } - - override fun onPause() { - Log.d(LOG_ID, "onPause called") - try { - preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) - InternalNotifier.remNotifier(requireContext(), this) - super.onPause() - } catch (exc: Exception) { - Log.e(LOG_ID, "onPause exception: " + exc.toString()) - } - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { - Log.d(LOG_ID, "onSharedPreferenceChanged called for " + key) - try { - if(DataSourceTask.preferencesToSend.contains(key)) - settingsChanged = true - - when(key) { - Constants.SHARED_PREF_LIBRE_PASSWORD, - Constants.SHARED_PREF_LIBRE_USER, - Constants.SHARED_PREF_DEXCOM_SHARE_USER, - Constants.SHARED_PREF_DEXCOM_SHARE_PASSWORD, - Constants.SHARED_PREF_NIGHTSCOUT_URL -> { - updateEnableStates(sharedPreferences!!) - update() - } - Constants.SHARED_PREF_LIBRE_PATIENT_ID -> { - update() - } - } - } catch (exc: Exception) { - Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.toString()) - } - } - - fun updateEnableStates(sharedPreferences: SharedPreferences) { - try { - val switchLibreSource = findPreference(Constants.SHARED_PREF_LIBRE_ENABLED) - if (switchLibreSource != null) { - val libreUser = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_USER, "")!!.trim() - val librePassword = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_PASSWORD, "")!!.trim() - switchLibreSource.isEnabled = libreUser.isNotEmpty() && librePassword.isNotEmpty() - if(!switchLibreSource.isEnabled) - switchLibreSource.isChecked = false - } - - val switchDexcomSource = findPreference(Constants.SHARED_PREF_DEXCOM_SHARE_ENABLED) - if (switchDexcomSource != null) { - val dexcomUser = sharedPreferences.getString(Constants.SHARED_PREF_DEXCOM_SHARE_USER, "")!!.trim() - val dexcomPassword = sharedPreferences.getString(Constants.SHARED_PREF_DEXCOM_SHARE_PASSWORD, "")!!.trim() - switchDexcomSource.isEnabled = dexcomUser.isNotEmpty() && dexcomPassword.isNotEmpty() - if(!switchDexcomSource.isEnabled) - switchDexcomSource.isChecked = false - } - - val switchNightscoutSource = findPreference(Constants.SHARED_PREF_NIGHTSCOUT_ENABLED) - if (switchNightscoutSource != null) { - val url = sharedPreferences.getString(Constants.SHARED_PREF_NIGHTSCOUT_URL, "")!!.trim() - switchNightscoutSource.isEnabled = url.isNotEmpty() && url.isNotEmpty() - if(!switchNightscoutSource.isEnabled) - switchNightscoutSource.isChecked = false - } - } catch (exc: Exception) { - Log.e(LOG_ID, "updateEnableStates exception: " + exc.toString()) - } - } - - private fun setSummary(key: String, defaultResId: Int) { - val pref = findPreference(key) - if(pref != null) { - val value = preferenceManager.sharedPreferences!!.getString(key, "")!!.trim() - pref.summary = if(value.isEmpty()) - resources.getString(defaultResId) - else - value - } - } - private fun update() { - setSummary(Constants.SHARED_PREF_LIBRE_USER, CR.string.src_libre_user_summary) - setSummary(Constants.SHARED_PREF_NIGHTSCOUT_URL, CR.string.src_ns_url_summary) - setPatientSummary() - } - - - private fun setPatientSummary() { - val listPreference = findPreference(Constants.SHARED_PREF_LIBRE_PATIENT_ID) - if(listPreference != null && listPreference.isVisible) { - val pref = findPreference(Constants.SHARED_PREF_LIBRE_PATIENT_ID) - if (pref != null) { - val value = preferenceManager.sharedPreferences!!.getString( - Constants.SHARED_PREF_LIBRE_PATIENT_ID, - "" - )!!.trim() - if (value.isEmpty() || !LibreLinkSourceTask.patientData.containsKey(value)) - pref.summary = resources.getString(CR.string.src_libre_patient_summary) - else { - pref.summary = LibreLinkSourceTask.patientData[value] - } - } - } - } - private fun setupLibrePatientData() { - try { - val listPreference = findPreference(Constants.SHARED_PREF_LIBRE_PATIENT_ID) - // force "global broadcast" to be the first entry - listPreference!!.entries = LibreLinkSourceTask.patientData.values.toTypedArray() - listPreference.entryValues = LibreLinkSourceTask.patientData.keys.toTypedArray() - listPreference.isVisible = LibreLinkSourceTask.patientData.size > 1 - if(listPreference.isVisible) - setPatientSummary() - } catch (exc: Exception) { - Log.e(LOG_ID, "setupLibrePatientData exception: $exc") - } - } - - override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) { - try { - Log.v(LOG_ID, "OnNotifyData called for source $dataSource") - if (dataSource == NotifySource.PATIENT_DATA_CHANGED) - setupLibrePatientData() - } catch (exc: Exception) { - Log.e(LOG_ID, "OnNotifyData exception for source $dataSource: $exc") - } - } - -} \ No newline at end of file diff --git a/auto/src/main/res/layout-land/activity_main.xml b/auto/src/main/res/layout-land/activity_main.xml index cc00caba1..d9f46ab45 100644 --- a/auto/src/main/res/layout-land/activity_main.xml +++ b/auto/src/main/res/layout-land/activity_main.xml @@ -93,6 +93,38 @@ android:autoSizeTextType="uniform" tools:ignore="HardcodedText" /> + + + + + + android:text="@string/alarm_header"/> diff --git a/auto/src/main/res/layout/activity_main.xml b/auto/src/main/res/layout/activity_main.xml index 4c3cdcbbe..4bea91f4e 100644 --- a/auto/src/main/res/layout/activity_main.xml +++ b/auto/src/main/res/layout/activity_main.xml @@ -82,6 +82,38 @@ android:autoSizeTextType="uniform" tools:ignore="HardcodedText" /> + + + + + + android:text="@string/alarm_header"/> diff --git a/auto/src/main/res/menu/menu_items.xml b/auto/src/main/res/menu/menu_items.xml index 3f65150fd..1d8785e6f 100644 --- a/auto/src/main/res/menu/menu_items.xml +++ b/auto/src/main/res/menu/menu_items.xml @@ -51,6 +51,13 @@ + + + + + diff --git a/auto/src/main/res/xml/pref_general.xml b/auto/src/main/res/xml/pref_general.xml index e1dcc590a..1a10679c6 100644 --- a/auto/src/main/res/xml/pref_general.xml +++ b/auto/src/main/res/xml/pref_general.xml @@ -24,9 +24,19 @@ app:showSeekBarValue="true" app:seekBarIncrement="1" app:min="2" - android:max="12" + android:max="16" android:key="obsolete_time" android:title="@string/obsolete_time" android:summary="@string/obsolete_time_summary" app:iconSpaceReserved="false" /> + + + diff --git a/auto/src/main/res/xml/sources.xml b/auto/src/main/res/xml/sources.xml index e671df68a..c66778ea6 100644 --- a/auto/src/main/res/xml/sources.xml +++ b/auto/src/main/res/xml/sources.xml @@ -11,6 +11,6 @@ android:key="pref_source_online" android:title="@string/pref_cat_follower" android:summary="@string/pref_cat_follower_summary" - app:fragment="de.michelinside.glucodataauto.preferences.SourceOnlineFragment" + app:fragment="de.michelinside.glucodatahandler.common.preferences.SourceOnlineFragment" app:iconSpaceReserved="false" /> \ No newline at end of file diff --git a/auto/src/main/res/xml/sources_offline.xml b/auto/src/main/res/xml/sources_offline.xml index 53688b5da..45e7bbae5 100644 --- a/auto/src/main/res/xml/sources_offline.xml +++ b/auto/src/main/res/xml/sources_offline.xml @@ -2,11 +2,12 @@ @@ -35,6 +36,11 @@ android:title="@string/pref_source_activate_local_nightscout_iob" android:summary="@string/pref_source_activate_local_nightscout_iob_summary" app:iconSpaceReserved="false" /> + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/wear/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/auto/src/second/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 73% rename from wear/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to auto/src/second/res/mipmap-anydpi-v26/ic_launcher.xml index c4a603d4c..036d09bc5 100644 --- a/wear/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/auto/src/second/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/auto/src/second/res/mipmap-hdpi/ic_launcher_foreground.png b/auto/src/second/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..2ffc6e3c8 Binary files /dev/null and b/auto/src/second/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/auto/src/second/res/mipmap-mdpi/ic_launcher_foreground.png b/auto/src/second/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..e042df76c Binary files /dev/null and b/auto/src/second/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/auto/src/second/res/mipmap-xhdpi/ic_launcher_foreground.png b/auto/src/second/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..d393621a8 Binary files /dev/null and b/auto/src/second/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/auto/src/second/res/mipmap-xxhdpi/ic_launcher_foreground.png b/auto/src/second/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..131dd7f30 Binary files /dev/null and b/auto/src/second/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/auto/src/second/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/auto/src/second/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..3af0fe20a Binary files /dev/null and b/auto/src/second/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/auto/src/second/res/values/ic_launcher_background.xml b/auto/src/second/res/values/ic_launcher_background.xml new file mode 100644 index 000000000..c5d5899fd --- /dev/null +++ b/auto/src/second/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 488994081..4347938ed 100644 --- a/build.gradle +++ b/build.gradle @@ -8,8 +8,8 @@ plugins { Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) -project.ext.set("versionCode", 79) -project.ext.set("versionName", "1.2.1") +project.ext.set("versionCode", 98) +project.ext.set("versionName", "1.3") project.ext.set("compileSdk", 34) project.ext.set("targetSdk", 34) project.ext.set("minSdk", 26) diff --git a/common/build.gradle b/common/build.gradle index 35c917f3f..b6a16a7c7 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -45,7 +45,7 @@ dependencies { implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'com.google.android.material:material:1.12.0' - implementation 'com.google.android.gms:play-services-wearable:18.2.0' + implementation 'com.google.android.gms:play-services-wearable:19.0.0' implementation 'androidx.work:work-runtime:2.9.1' implementation 'androidx.preference:preference-ktx:1.2.1' testImplementation 'junit:junit:4.13.2' diff --git a/common/src/androidTest/java/de/michelinside/glucodatahandler/common/ReceiveDataTest.kt b/common/src/androidTest/java/de/michelinside/glucodatahandler/common/ReceiveDataTest.kt index d57935b0b..65cf69d5c 100644 --- a/common/src/androidTest/java/de/michelinside/glucodatahandler/common/ReceiveDataTest.kt +++ b/common/src/androidTest/java/de/michelinside/glucodatahandler/common/ReceiveDataTest.kt @@ -4,6 +4,7 @@ import android.os.Bundle import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import de.michelinside.glucodatahandler.common.notifier.DataSource +import de.michelinside.glucodatahandler.common.utils.GlucoDataUtils import org.junit.Test import org.junit.runner.RunWith @@ -56,6 +57,7 @@ class ReceiveDataTest { fun testChangeToMmol() { // Context of the app under test. ReceiveData.changeIsMmol(false, appContext) + ReceiveData.time = 0L // force first value handling assertFalse(ReceiveData.isMmol) val glucoExtras = Bundle() glucoExtras.putLong(ReceiveData.TIME, ReceiveData.time + 60000) @@ -70,9 +72,29 @@ class ReceiveDataTest { assertTrue(ReceiveData.isMmol) } + + @Test + fun testMmolCalculation() { + ReceiveData.changeIsMmol(true, appContext) + if(ReceiveData.time == 0L) + ReceiveData.time = 1L // prevent changing unit! + ReceiveData.glucose = 0F // set to 0 to check, if it gets overwritten + val glucoExtras = Bundle() + glucoExtras.putLong(ReceiveData.TIME, ReceiveData.time + 60000) + glucoExtras.putInt(ReceiveData.MGDL,180) + glucoExtras.putFloat(ReceiveData.RATE, -2F) + assertTrue(ReceiveData.handleIntent(appContext, DataSource.NONE, glucoExtras)) + assertEquals(180, ReceiveData.rawValue) + assertEquals(GlucoDataUtils.mgToMmol(180F), ReceiveData.glucose) + assertEquals(-2F, ReceiveData.rate) + assertTrue(ReceiveData.isMmol) + } + + @Test fun testReceiveInternalGlucoseAlarm() { // Context of the app under test. + ReceiveData.changeIsMmol(false, appContext) val glucoExtras = Bundle() glucoExtras.putLong(ReceiveData.TIME, ReceiveData.time + 60000) glucoExtras.putInt(ReceiveData.MGDL,280) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt index bcdd9935e..17378091b 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt @@ -21,7 +21,7 @@ object Constants { const val COMMAND_BUNDLE = "command_bundle" const val GLUCOSE_CONVERSION_FACTOR = 18.0182F const val GLUCOSE_MIN_VALUE = 40 - const val GLUCOSE_MAX_VALUE = 400 + const val GLUCOSE_MAX_VALUE = 600 const val ACTION_STOP_FOREGROUND = "stop_foreground" const val ACTION_PREFIX = "gdh_action_" @@ -29,7 +29,10 @@ object Constants { const val ACTION_DUMMY_VALUE = ACTION_PREFIX + "dummy_value" const val ACTION_SPEAK = ACTION_PREFIX + "speak" - const val PACKAGE_GLUCODATAAUTO = "de.michelinside.glucodataauto" + const val IS_SECOND = BuildConfig.BUILD_TYPE == "second" + + val PACKAGE_GLUCODATAHANDLER = if (IS_SECOND) "de.michelinside.glucodatahandler.second" else "de.michelinside.glucodatahandler" + val PACKAGE_GLUCODATAAUTO = if (IS_SECOND) "de.michelinside.glucodataauto.second" else "de.michelinside.glucodataauto" const val PACKAGE_JUGGLUCO = "tk.glucodata" const val EXTRA_SOURCE_PACKAGE = "gdh.source_package" @@ -61,13 +64,6 @@ object Constants { const val SHARED_PREF_XDRIP_BROADCAST_SERVICE_API = "xdrip_broadcast_service_api" const val SHARED_PREF_TARGET_MIN = "target_min_value" const val SHARED_PREF_TARGET_MAX = "target_max_value" - const val SHARED_PREF_CAR_NOTIFICATION = "car_notification" - const val SHARED_PREF_CAR_NOTIFICATION_INTERVAL = "car_notification_interval" // deprecated as changed to seekbar - const val SHARED_PREF_CAR_NOTIFICATION_ALARM_ONLY = "car_notification_alarm_only" - const val SHARED_PREF_CAR_NOTIFICATION_INTERVAL_NUM = "car_notification_interval_num" - const val SHARED_PREF_CAR_NOTIFICATION_REAPPEAR_INTERVAL = "car_notification_reappear_interval" - const val SHARED_PREF_CAR_MEDIA = "car_media" - const val SHARED_PREF_CAR_MEDIA_ICON_STYLE = "aa_media_player_icon_style" const val SHARED_PREF_USE_MMOL = "use_mmol" const val SHARED_PREF_GLUCODATA_RECEIVER_SHOW_ALL = "show_all_glucodata_receivers" const val SHARED_PREF_LOW_GLUCOSE = "low_glucose" @@ -95,6 +91,7 @@ object Constants { const val SHARED_PREF_FLOATING_WIDGET = "floating_widget" const val SHARED_PREF_FLOATING_WIDGET_STYLE = "floating_widget_style" const val SHARED_PREF_FLOATING_WIDGET_SIZE = "floating_widget_size" + const val SHARED_PREF_FLOATING_WIDGET_SIZE_MIGRATION = "floating_widget_size_migration" const val SHARED_PREF_FLOATING_WIDGET_TRANSPARENCY = "floating_widget_transparency" const val SHARED_PREF_FLOATING_WIDGET_TIME_TO_CLOSE = "floating_widget_time_to_close" const val SHARED_PREF_FLOATING_WIDGET_TAP_ACTION = "floating_widget_tap_action" @@ -117,6 +114,11 @@ object Constants { const val SHARED_PREF_SAVE_WEAR_LOGS = "save_wear_logs" + const val SHARED_PREF_WATCHFACES_PUJIE = "pref_watchfaces_pujie" + const val SHARED_PREF_WATCHFACES_DMM = "pref_watchfaces_dmm" + const val SHARED_PREF_WATCHFACES_GDC = "pref_watchfaces_gdc" + + // internal app preferences (not changed by settings) -> use separate tag for not trigger onChanged events const val SHARED_PREF_INTERNAL_TAG = "GlucoDataHandlerInternalAppPrefs" const val SHARED_PREF_FLOATING_WIDGET_X = "floating_widget_x" @@ -155,6 +157,8 @@ object Constants { const val SHARED_PREF_LIBRE_REGION="source_libre_region" const val SHARED_PREF_LIBRE_PATIENT_ID="source_libre_patient_id" const val SHARED_PREF_LIBRE_USER_ID="source_libre_user_id" + const val SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU="source_libre_auto_accept_tou" + const val SHARED_PREF_LIBRE_SERVER="source_libre_server" const val SHARED_PREF_DEXCOM_SHARE_ENABLED="source_dexcom_share_enabled" const val SHARED_PREF_DEXCOM_SHARE_USER="source_dexcom_share_user" @@ -198,11 +202,13 @@ object Constants { const val SHARED_PREF_ALARM_START_DELAY = "alarm_start_delay" const val SHARED_PREF_ALARM_START_DELAY_STRING = "alarm_start_delay_string" const val SHARED_PREF_NOTIFICATION_AUTO_CLOSE = "auto_close_notification" + const val SHARED_PREF_ALARM_FORCE_VERY_LOW = "alarm_force_very_low" const val SHARED_PREF_ALARM_NOTIFICATION_ENABLED = "alarm_notifications_enabled" const val SHARED_PREF_ALARM_FULLSCREEN_NOTIFICATION_ENABLED = "alarm_fullscreen_notification_enabled" const val SHARED_PREF_ALARM_FULLSCREEN_DISMISS_KEYGUARD = "alarm_fullscreen_dismiss_keyguard" const val SHARED_PREF_ALARM_SNOOZE_ON_NOTIFICATION = "alarm_snooze_on_notification" + const val SHARED_PREF_ALARM_SNOOZE_NOTIFICATION_BUTTONS = "alarm_snooze_notification_buttons" const val SHARED_PREF_ALARM_FORCE_SOUND = "alarm_force_sound" const val SHARED_PREF_ALARM_FORCE_VIBRATION = "alarm_force_vibration" const val SHARED_PREF_ALARM_INACTIVE_ENABLED = "alarm_inactive_enabled" @@ -218,7 +224,6 @@ object Constants { const val SHARED_PREF_ALARM_SUFFIX_ENABLED = "_enabled" const val SHARED_PREF_ALARM_SUFFIX_INTERVAL = "_interval" - const val SHARED_PREF_ALARM_SUFFIX_RETRIGGER = "_retrigger" const val SHARED_PREF_ALARM_SUFFIX_USE_CUSTOM_SOUND = "_use_custom_sound" const val SHARED_PREF_ALARM_SUFFIX_CUSTOM_SOUND = "_custom_sound" const val SHARED_PREF_ALARM_SUFFIX_VIBRATE_PATTERN = "_vibrate_pattern" @@ -232,6 +237,7 @@ object Constants { const val SHARED_PREF_ALARM_SUFFIX_TEST = "_test" const val SHARED_PREF_ALARM_SUFFIX_SAVE_SOUND = "_save_sound" const val SHARED_PREF_ALARM_SUFFIX_REPEAT = "_repeat" + const val SHARED_PREF_ALARM_SUFFIX_REPEAT_UNTIL_CLOSE = "_repeat_until_close" const val SHARED_PREF_ALARM_SUFFIX_DELTA = "_delta" const val SHARED_PREF_ALARM_SUFFIX_OCCURRENCE_COUNT = "_occurrence_count" const val SHARED_PREF_ALARM_SUFFIX_BORDER = "_border" @@ -246,16 +252,36 @@ object Constants { const val SHARED_PREF_ALARM_TYPE_SETTINGS_CAT = "cat_alarm_settings" + const val SHARED_PREF_BATTERY_RECEIVER_ENABLED = "battery_receiver_enabled" + const val SHARED_PREF_SEND_TO_WATCH_INTERVAL = "send_to_watch_interval" + const val SHARED_PREF_SEND_TO_RECEIVER_INTERVAL = "send_to_receiver_interval" + + const val SHARED_PREF_PHONE_WEAR_SCREEN_OFF_UPDATE = "phone_wear_screen_off_update" + + const val SHARED_PREF_DISCLAIMER_SHOWN = "gdh_disclaimer_shown" + // Android Auto const val AA_MEDIA_ICON_STYLE_TREND = "trend" const val AA_MEDIA_ICON_STYLE_GLUCOSE_TREND = "glucose_trend" const val AA_MEDIA_ICON_STYLE_GLUCOSE = "glucose" const val SHARED_PREF_FOREGROUND_SERVICE = "foreground_service" + const val SHARED_PREF_CAR_NOTIFICATION = "car_notification" + const val SHARED_PREF_CAR_NOTIFICATION_INTERVAL = "car_notification_interval" // deprecated as changed to seekbar + const val SHARED_PREF_CAR_NOTIFICATION_ALARM_ONLY = "car_notification_alarm_only" + const val SHARED_PREF_CAR_NOTIFICATION_INTERVAL_NUM = "car_notification_interval_num" + const val SHARED_PREF_CAR_NOTIFICATION_REAPPEAR_INTERVAL = "car_notification_reappear_interval" + const val SHARED_PREF_CAR_NOTIFICATION_SHOW_IOB_COB = "car_notification_show_iob_cob" + + const val SHARED_PREF_CAR_MEDIA = "car_media" + const val AA_MEDIA_ICON_STYLE = "aa_media_player_icon_style" + const val AA_MEDIA_SHOW_IOB_COB = "aa_media_player_show_iob_cob" const val AA_MEDIA_PLAYER_SPEAK_VALUES = "aa_media_player_speak_values" const val AA_MEDIA_PLAYER_SPEAK_NEW_VALUE = "aa_media_player_speak_new_value" const val AA_MEDIA_PLAYER_SPEAK_ALARM_ONLY = "aa_media_player_speak_alarm_only" const val AA_MEDIA_PLAYER_SPEAK_INTERVAL = "aa_media_player_speak_interval" const val AA_MEDIA_PLAYER_SPEAK_TEST = "aa_media_player_speak_test" const val AA_MEDIA_PLAYER_DURATION = "aa_media_player_duration" + + const val PATIENT_NAME = "patient_name" } diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt index c0320bd83..8fa2d4d68 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt @@ -12,7 +12,6 @@ import android.os.Bundle import android.util.Log import androidx.annotation.RequiresApi import com.google.android.gms.wearable.WearableListenerService -import de.michelinside.glucodatahandler.common.ReceiveData.isMmol import de.michelinside.glucodatahandler.common.notification.ChannelType import de.michelinside.glucodatahandler.common.notification.Channels import de.michelinside.glucodatahandler.common.notifier.DataSource @@ -26,6 +25,7 @@ import de.michelinside.glucodatahandler.common.receiver.DiaboxReceiver import de.michelinside.glucodatahandler.common.receiver.GlucoseDataReceiver import de.michelinside.glucodatahandler.common.receiver.NsEmulatorReceiver import de.michelinside.glucodatahandler.common.receiver.ReceiverBase +import de.michelinside.glucodatahandler.common.receiver.ScreenEventReceiver import de.michelinside.glucodatahandler.common.receiver.XDripBroadcastReceiver import de.michelinside.glucodatahandler.common.tasks.BackgroundWorker import de.michelinside.glucodatahandler.common.tasks.SourceTaskService @@ -45,7 +45,8 @@ enum class AppSource { } abstract class GlucoDataService(source: AppSource) : WearableListenerService(), SharedPreferences.OnSharedPreferenceChangeListener { - private lateinit var batteryReceiver: BatteryReceiver + protected var batteryReceiver: BatteryReceiver? = null + protected var screenEventReceiver: ScreenEventReceiver? = null private lateinit var broadcastServiceAPI: BroadcastServiceAPI @SuppressLint("StaticFieldLeak") @@ -373,19 +374,29 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService(), } } } + + if(sharedPrefs.contains(Constants.SHARED_PREF_ALARM_SNOOZE_ON_NOTIFICATION)) { + with(sharedPrefs.edit()) { + remove(Constants.SHARED_PREF_ALARM_SNOOZE_ON_NOTIFICATION) + putStringSet(Constants.SHARED_PREF_ALARM_SNOOZE_NOTIFICATION_BUTTONS, mutableSetOf("60", "90", "120")) + apply() + } + } + } fun getSettings(): Bundle { val bundle = ReceiveData.getSettingsBundle() // other settings if (sharedPref != null) { - bundle.putBoolean(Constants.SHARED_PREF_SHOW_OTHER_UNIT, sharedPref!!.getBoolean(Constants.SHARED_PREF_SHOW_OTHER_UNIT, isMmol)) + bundle.putBoolean(Constants.SHARED_PREF_SHOW_OTHER_UNIT, sharedPref!!.getBoolean(Constants.SHARED_PREF_SHOW_OTHER_UNIT, ReceiveData.isMmol)) bundle.putBoolean(Constants.SHARED_PREF_SOURCE_JUGGLUCO_ENABLED, sharedPref!!.getBoolean(Constants.SHARED_PREF_SOURCE_JUGGLUCO_ENABLED, true)) bundle.putBoolean(Constants.SHARED_PREF_SOURCE_XDRIP_ENABLED, sharedPref!!.getBoolean(Constants.SHARED_PREF_SOURCE_XDRIP_ENABLED, true)) bundle.putBoolean(Constants.SHARED_PREF_SOURCE_AAPS_ENABLED, sharedPref!!.getBoolean(Constants.SHARED_PREF_SOURCE_AAPS_ENABLED, true)) bundle.putBoolean(Constants.SHARED_PREF_SOURCE_BYODA_ENABLED, sharedPref!!.getBoolean(Constants.SHARED_PREF_SOURCE_BYODA_ENABLED, true)) bundle.putBoolean(Constants.SHARED_PREF_SOURCE_EVERSENSE_ENABLED, sharedPref!!.getBoolean(Constants.SHARED_PREF_SOURCE_EVERSENSE_ENABLED, true)) bundle.putBoolean(Constants.SHARED_PREF_SOURCE_DIABOX_ENABLED, sharedPref!!.getBoolean(Constants.SHARED_PREF_SOURCE_DIABOX_ENABLED, true)) + bundle.putBoolean(Constants.SHARED_PREF_PHONE_WEAR_SCREEN_OFF_UPDATE, sharedPref!!.getBoolean(Constants.SHARED_PREF_PHONE_WEAR_SCREEN_OFF_UPDATE, true)) } Log.v(LOG_ID, "getSettings called with bundle ${(Utils.dumpBundle(bundle))}") return bundle @@ -395,13 +406,14 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService(), Log.v(LOG_ID, "setSettings called with bundle ${(Utils.dumpBundle(bundle))}") val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) with(sharedPref!!.edit()) { - putBoolean(Constants.SHARED_PREF_SHOW_OTHER_UNIT, bundle.getBoolean(Constants.SHARED_PREF_SHOW_OTHER_UNIT, isMmol)) + putBoolean(Constants.SHARED_PREF_SHOW_OTHER_UNIT, bundle.getBoolean(Constants.SHARED_PREF_SHOW_OTHER_UNIT, ReceiveData.isMmol)) putBoolean(Constants.SHARED_PREF_SOURCE_JUGGLUCO_ENABLED, bundle.getBoolean(Constants.SHARED_PREF_SOURCE_JUGGLUCO_ENABLED, true)) putBoolean(Constants.SHARED_PREF_SOURCE_XDRIP_ENABLED, bundle.getBoolean(Constants.SHARED_PREF_SOURCE_XDRIP_ENABLED, true)) putBoolean(Constants.SHARED_PREF_SOURCE_AAPS_ENABLED, bundle.getBoolean(Constants.SHARED_PREF_SOURCE_AAPS_ENABLED, true)) putBoolean(Constants.SHARED_PREF_SOURCE_BYODA_ENABLED, bundle.getBoolean(Constants.SHARED_PREF_SOURCE_BYODA_ENABLED, true)) putBoolean(Constants.SHARED_PREF_SOURCE_EVERSENSE_ENABLED, bundle.getBoolean(Constants.SHARED_PREF_SOURCE_EVERSENSE_ENABLED, true)) putBoolean(Constants.SHARED_PREF_SOURCE_DIABOX_ENABLED, bundle.getBoolean(Constants.SHARED_PREF_SOURCE_DIABOX_ENABLED, true)) + putBoolean(Constants.SHARED_PREF_PHONE_WEAR_SCREEN_OFF_UPDATE, bundle.getBoolean(Constants.SHARED_PREF_PHONE_WEAR_SCREEN_OFF_UPDATE, true)) apply() } ReceiveData.setSettings(sharedPref, bundle) @@ -464,8 +476,8 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService(), updateSourceReceiver(this) broadcastServiceAPI = BroadcastServiceAPI() broadcastServiceAPI.init() - batteryReceiver = BatteryReceiver() - registerReceiver(batteryReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) + updateBatteryReceiver() + updateScreenReceiver() sharedPref!!.registerOnSharedPreferenceChangeListener(this) @@ -499,7 +511,14 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService(), sharedPref!!.unregisterOnSharedPreferenceChangeListener(this) unregisterSourceReceiver(this) broadcastServiceAPI.close(this) - unregisterReceiver(batteryReceiver) + if(batteryReceiver != null) { + unregisterReceiver(batteryReceiver) + batteryReceiver = null + } + if(screenEventReceiver != null) { + unregisterReceiver(screenEventReceiver) + screenEventReceiver = null + } TimeTaskService.stop() SourceTaskService.stop() connection!!.close() @@ -515,6 +534,32 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService(), } } + open fun updateBatteryReceiver() { + try { + if (sharedPref!!.getBoolean(Constants.SHARED_PREF_BATTERY_RECEIVER_ENABLED, true)) { + if(batteryReceiver == null) { + Log.i(LOG_ID, "register batteryReceiver") + batteryReceiver = BatteryReceiver() + registerReceiver(batteryReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) + } + } else if(batteryReceiver != null) { + Log.i(LOG_ID, "unregister batteryReceiver") + unregisterReceiver(batteryReceiver) + batteryReceiver = null + // notify new battery level to update UI + BatteryReceiver.batteryPercentage = 0 + InternalNotifier.notify(this, NotifySource.BATTERY_LEVEL, BatteryReceiver.batteryBundle) + } + } catch (exc: Exception) { + Log.e(LOG_ID, "updateBatteryReceiver exception: " + exc.toString()) + } + } + + open fun updateScreenReceiver() { + // do nothing here, only so sub-classes + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { try { Log.d(LOG_ID, "onSharedPreferenceChanged called with key $key") @@ -532,6 +577,13 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService(), Constants.SHARED_PREF_SHOW_OTHER_UNIT -> { shareSettings = true } + Constants.SHARED_PREF_BATTERY_RECEIVER_ENABLED -> { + updateBatteryReceiver() + } + Constants.SHARED_PREF_PHONE_WEAR_SCREEN_OFF_UPDATE -> { + updateScreenReceiver() + shareSettings = true + } } if (shareSettings) { val extras = Bundle() diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/ReceiveData.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/ReceiveData.kt index d53dd7249..939797194 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/ReceiveData.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/ReceiveData.kt @@ -10,6 +10,7 @@ import de.michelinside.glucodatahandler.common.notification.AlarmNotificationBas import de.michelinside.glucodatahandler.common.notification.AlarmType import de.michelinside.glucodatahandler.common.notifier.* import de.michelinside.glucodatahandler.common.notifier.DataSource +import de.michelinside.glucodatahandler.common.receiver.ScreenEventReceiver import de.michelinside.glucodatahandler.common.tasks.ElapsedTimeTask import de.michelinside.glucodatahandler.common.tasks.TimeTaskService import de.michelinside.glucodatahandler.common.utils.GlucoDataUtils @@ -54,6 +55,11 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { return (forceGlucoseAlarm || forceDeltaAlarm) } + var forceObsoleteOnScreenOff: Boolean = false + private val forceObsolete: Boolean get() { + return forceObsoleteOnScreenOff && ScreenEventReceiver.isDisplayOff() + } + var iob: Float = Float.NaN var cob: Float = Float.NaN var iobCobTime: Long = 0 @@ -175,8 +181,8 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { fun isObsoleteTime(timeoutSec: Int): Boolean = (System.currentTimeMillis()- time) >= (timeoutSec * 1000) - fun isObsoleteShort(): Boolean = isObsoleteTime(obsoleteTimeMin*60) - fun isObsoleteLong(): Boolean = isObsoleteTime(obsoleteTimeMin*120) + fun isObsoleteShort(): Boolean = forceObsolete || isObsoleteTime(obsoleteTimeMin*60) + fun isObsoleteLong(): Boolean = forceObsolete || isObsoleteTime(obsoleteTimeMin*120) fun isIobCobObsolete(timeoutSec: Int = Constants.VALUE_IOB_COBOBSOLETE_SEC): Boolean = (System.currentTimeMillis()- iobCobTime) >= (timeoutSec * 1000) fun getGlucoseAsString(): String { @@ -386,7 +392,7 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { } fun getElapsedTimeMinute(roundingMode: RoundingMode = RoundingMode.DOWN): Long { - return Utils.round((System.currentTimeMillis()-time).toFloat()/60000, 0, roundingMode).toLong() + return Utils.getElapsedTimeMinute(time, roundingMode) } fun getElapsedIobCobTimeMinute(roundingMode: RoundingMode = RoundingMode.HALF_UP): Long { return Utils.round((System.currentTimeMillis()- iobCobTime).toFloat()/60000, 0, roundingMode).toLong() @@ -402,7 +408,7 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { } fun getElapsedTimeMinuteAsString(context: Context, short: Boolean = true): String { - if (time == 0L) + if (time == 0L || forceObsolete) return "--" return if (ElapsedTimeTask.relativeTime) { getElapsedRelativeTimeAsString(context) @@ -412,11 +418,27 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { DateFormat.getTimeInstance(DateFormat.DEFAULT).format(Date(time)) } + fun hasNewValue(extras: Bundle?, checkIobCob: Boolean = true): Boolean { + if(extras == null || extras.isEmpty) + return false + if(extras.containsKey(TIME) && isNewValueTime(extras.getLong(TIME))) { + return true + } + if(!checkIobCob) + return false + return hasNewIobCob(extras) + } + + private fun isNewValueTime(newTime: Long): Boolean { + return getTimeDiffMinute(newTime) >= 1 + } + fun handleIntent(context: Context, dataSource: DataSource, extras: Bundle?, interApp: Boolean = false) : Boolean { if (extras == null || extras.isEmpty) { return false } + forceObsoleteOnScreenOff = false var result = false WakeLockHelper(context).use { initData(context) @@ -434,13 +456,15 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { return false } - if(getTimeDiffMinute(new_time) >= 1) // check for new value received (diff must around one minute at least to prevent receiving same data from different sources with similar timestamps + if(isNewValueTime(new_time)) // check for new value received (diff must around one minute at least to prevent receiving same data from different sources with similar timestamps { Log.i( LOG_ID, "Glucodata received from " + dataSource.toString() + ": " + extras.toString() + " - timestamp: " + DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT).format(Date(extras.getLong(TIME))) ) + if(time == 0L) + handleFirstValue(context, dataSource, extras) receiveTime = System.currentTimeMillis() source = dataSource sensorID = extras.getString(SERIAL) //Name of sensor @@ -456,7 +480,7 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { if (timeDiffMinute == 0L) { Log.w(LOG_ID, "Time diff is less than a minute! Can not calculate delta value!") deltaValue = Float.NaN - } else if (timeDiffMinute > 10L) { + } else if (timeDiffMinute > 20L) { deltaValue = Float.NaN // no delta calculation for too high time diffs } else { deltaValue = (extras.getInt(MGDL) - rawValue).toFloat() @@ -472,15 +496,16 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { } rawValue = extras.getInt(MGDL) - if (extras.containsKey(GLUCOSECUSTOM)) { - glucose = Utils.round(extras.getFloat(GLUCOSECUSTOM), 1) //Glucose value in unit in setting - changeIsMmol(rawValue!=glucose.toInt() && GlucoDataUtils.isMmolValue(glucose), context) - } else { - if (isMmol) { - glucose = GlucoDataUtils.mgToMmol(rawValue.toFloat()) - } else { - glucose = rawValue.toFloat() + if (isMmol) { + if (extras.containsKey(GLUCOSECUSTOM)) { + glucose = Utils.round(extras.getFloat(GLUCOSECUSTOM), 1) + if(!GlucoDataUtils.isMmolValue(glucose)) + glucose = GlucoDataUtils.mgToMmol(rawValue.toFloat()) } + else + glucose = GlucoDataUtils.mgToMmol(rawValue.toFloat()) + } else { + glucose = rawValue.toFloat() } time = extras.getLong(TIME) //time in msec @@ -540,12 +565,11 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { return result } - fun handleIobCob(context: Context, dataSource: DataSource, extras: Bundle, interApp: Boolean = false) { - Log.v(LOG_ID, "handleIobCob for source " + dataSource + ": " + extras.toString()) - if (extras.containsKey(IOB) || extras.containsKey(COB)) { + private fun hasNewIobCob(extras: Bundle?): Boolean { + if(extras != null && (extras.containsKey(IOB) || extras.containsKey(COB))) { if (!isIobCob() && extras.getFloat(IOB, Float.NaN).isNaN() && extras.getFloat(COB, Float.NaN).isNaN()) { Log.d(LOG_ID, "Ignore NaN IOB and COB") - return + return false } val newTime = if(extras.containsKey(IOBCOB_TIME)) @@ -554,24 +578,60 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { System.currentTimeMillis() if(!isIobCob() || (newTime > iobCobTime && (newTime-iobCobTime) > 30000)) { - var iobCobChange = false - if(iob != extras.getFloat(IOB, Float.NaN) || cob != extras.getFloat(COB, Float.NaN)) { - Log.i(LOG_ID, "Only IOB/COB changed: " + extras.getFloat(IOB, Float.NaN) + "/" + extras.getFloat(COB, Float.NaN)) - iob = extras.getFloat(IOB, Float.NaN) - cob = extras.getFloat(COB, Float.NaN) - iobCobChange = true - } else { - Log.d(LOG_ID, "Only IOB/COB time changed") - } - iobCobTime = newTime + return true + } + } + return false + } - // do not forward extras as interApp to prevent sending back to source... - val bundle: Bundle? = if(interApp) null else createExtras() // re-create extras to have all changed value inside for sending to receiver - if(iobCobChange) - InternalNotifier.notify(context, NotifySource.IOB_COB_CHANGE, bundle) - else - InternalNotifier.notify(context, NotifySource.IOB_COB_TIME, bundle) - saveExtras(context) + fun handleIobCob(context: Context, dataSource: DataSource, extras: Bundle, interApp: Boolean = false) { + Log.v(LOG_ID, "handleIobCob for source " + dataSource + ": " + extras.toString()) + if (hasNewIobCob(extras)) { + var iobCobChange = false + if(iob != extras.getFloat(IOB, Float.NaN) || cob != extras.getFloat(COB, Float.NaN)) { + Log.i(LOG_ID, "Only IOB/COB changed: " + extras.getFloat(IOB, Float.NaN) + "/" + extras.getFloat(COB, Float.NaN)) + iob = extras.getFloat(IOB, Float.NaN) + cob = extras.getFloat(COB, Float.NaN) + iobCobChange = true + } else { + Log.d(LOG_ID, "Only IOB/COB time changed") + } + iobCobTime = if(extras.containsKey(IOBCOB_TIME)) + extras.getLong(IOBCOB_TIME) + else + System.currentTimeMillis() + + // do not forward extras as interApp to prevent sending back to source... + val bundle: Bundle? = if(interApp) null else createExtras() // re-create extras to have all changed value inside for sending to receiver + if(iobCobChange) + InternalNotifier.notify(context, NotifySource.IOB_COB_CHANGE, bundle) + else + InternalNotifier.notify(context, NotifySource.IOB_COB_TIME, bundle) + saveExtras(context) + } + } + + private fun handleFirstValue(context: Context, dataSource: DataSource, extras: Bundle) { + // check unit and delta for first value, only! + Log.i(LOG_ID, "Handle first value") + val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) + if (extras.containsKey(GLUCOSECUSTOM)) { + val raw = extras.getInt(MGDL) + val glucoseCustom = Utils.round(extras.getFloat(GLUCOSECUSTOM), 1) //Glucose value in unit in setting + changeIsMmol(raw!=glucoseCustom.toInt() && GlucoDataUtils.isMmolValue(glucoseCustom), context) + } + if(!sharedPref.contains(Constants.SHARED_PREF_FIVE_MINUTE_DELTA) && dataSource.interval5Min) { + Log.i(LOG_ID, "Change delta to 5 min") + use5minDelta = true + with(sharedPref.edit()) { + putBoolean(Constants.SHARED_PREF_FIVE_MINUTE_DELTA, true) + apply() + } + } + if(dataSource == DataSource.DEXCOM_SHARE && !sharedPref.contains(Constants.SHARED_PREF_SOURCE_INTERVAL)) { + with(sharedPref.edit()) { + putString(Constants.SHARED_PREF_SOURCE_INTERVAL, "5") // use 5 min interval for dexcom share + apply() } } } @@ -579,16 +639,15 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { fun changeIsMmol(newValue: Boolean, context: Context? = null) { if (isMmol != newValue) { isMmolValue = newValue - if (isMmolValue != GlucoDataUtils.isMmolValue(glucose)) { + if (time > 0 && isMmolValue != GlucoDataUtils.isMmolValue(glucose)) { glucose = if (isMmolValue) GlucoDataUtils.mgToMmol(glucose) else GlucoDataUtils.mmolToMg(glucose) } - Log.i(LOG_ID, "Unit changed to " + glucose + if(isMmolValue) "mmol/l" else "mg/dl") + Log.i(LOG_ID, "Unit changed to " + glucose + if(isMmolValue) " mmol/l" else " mg/dl") if (context != null) { - val sharedPref = - context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) + val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) with(sharedPref.edit()) { putBoolean(Constants.SHARED_PREF_USE_MMOL, isMmol) apply() @@ -658,16 +717,18 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { private fun readTargets(context: Context) { val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) sharedPref.registerOnSharedPreferenceChangeListener(this) - if(!sharedPref.contains(Constants.SHARED_PREF_USE_MMOL)) { - Log.i(LOG_ID, "Upgrade to new mmol handling!") + if(!sharedPref.contains(Constants.SHARED_PREF_USE_MMOL) && (sharedPref.contains(Constants.SHARED_PREF_TARGET_MIN) || sharedPref.contains(Constants.SHARED_PREF_TARGET_MAX))) { + targetMinValue = sharedPref.getFloat(Constants.SHARED_PREF_TARGET_MIN, targetMinValue) + targetMaxValue = sharedPref.getFloat(Constants.SHARED_PREF_TARGET_MAX, targetMaxValue) isMmolValue = GlucoDataUtils.isMmolValue(targetMinValue) if (isMmol) { + Log.i(LOG_ID, "Upgrade to new mmol handling!") writeTarget(context, true, targetMinValue) writeTarget(context, false, targetMaxValue) - } - with(sharedPref.edit()) { - putBoolean(Constants.SHARED_PREF_USE_MMOL, isMmol) - apply() + with(sharedPref.edit()) { + putBoolean(Constants.SHARED_PREF_USE_MMOL, isMmol) + apply() + } } } updateSettings(sharedPref) @@ -692,7 +753,7 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { } } - fun createExtras(): Bundle? { + fun createExtras(includeObsoleteIobCob: Boolean = true): Bundle? { if(time == 0L) return null val extras = Bundle() @@ -703,9 +764,11 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { extras.putFloat(RATE, rate) extras.putInt(ALARM, alarm) extras.putFloat(DELTA, deltaValue) - extras.putFloat(IOB, iob) - extras.putFloat(COB, cob) - extras.putLong(IOBCOB_TIME, iobCobTime) + if(includeObsoleteIobCob || !isIobCobObsolete()) { + extras.putFloat(IOB, iob) + extras.putFloat(COB, cob) + extras.putLong(IOBCOB_TIME, iobCobTime) + } extras.putInt(DELTA_FALLING_COUNT, deltaFallingCount) extras.putInt(DELTA_RISING_COUNT, deltaRisingCount) return extras diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/SourceState.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/SourceState.kt index 95da66c75..cd9016097 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/SourceState.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/SourceState.kt @@ -20,20 +20,24 @@ object SourceStateData { var lastSource: DataSource = DataSource.NONE var lastState: SourceState = SourceState.NONE var lastError: String = "" + var lastErrorInfo: String = "" + var lastStateTime = 0L fun setError(source: DataSource, error: String) { setState( source, SourceState.ERROR, error) } - fun setState(source: DataSource, state: SourceState, error: String = "") { + fun setState(source: DataSource, state: SourceState, error: String = "", errorInfo: String = "") { lastError = error lastSource = source lastState = state + lastStateTime = System.currentTimeMillis() + lastErrorInfo = errorInfo if(state == SourceState.ERROR) { - Log.e(LOG_ID, "Error state for source $source: $error" ) + Log.e(LOG_ID, "Error state for source $source: $error - info: $errorInfo" ) } else { - Log.i(LOG_ID, "Set state $state for source $source") + Log.i(LOG_ID, "Set state $state for source $source: $error") } if (GlucoDataService.context != null) { @@ -52,15 +56,15 @@ object SourceStateData { } fun getStateMessage(context: Context): String { - if (lastState == SourceState.ERROR && lastError.isNotEmpty()) { + if (lastState != SourceState.NONE && lastError.isNotEmpty()) { return lastError } return context.getString(lastState.resId) } - + /* fun getState(context: Context): String { if (lastState == SourceState.NONE) return "" return "%s: %s".format(context.getString(lastSource.resId), getStateMessage(context)) - } + }*/ } \ No newline at end of file diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt index eac3ad547..d8b06099c 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt @@ -15,6 +15,7 @@ import de.michelinside.glucodatahandler.common.notifier.DataSource import de.michelinside.glucodatahandler.common.tasks.DataSourceTask import de.michelinside.glucodatahandler.common.utils.Utils import kotlinx.coroutines.* +import java.math.RoundingMode import java.text.DateFormat import java.util.* import kotlin.coroutines.cancellation.CancellationException @@ -25,12 +26,16 @@ enum class Command { SNOOZE_ALARM, TEST_ALARM, AA_CONNECTION_STATE, - DISABLE_INACTIVE_TIME + DISABLE_INACTIVE_TIME, + PAUSE_NODE, + RESUME_NODE, + FORCE_UPDATE } class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityClient.OnCapabilityChangedListener, NotifierInterface { private val LOG_ID = "GDH.WearPhoneConnection" private lateinit var context: Context + private var lastSendValuesTime = 0L private val capabilityName: String get() { if(GlucoDataService.appSource == AppSource.PHONE_APP) // phone sends to wear @@ -54,6 +59,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC private val nodeBatteryLevel = Collections.synchronizedMap(mutableMapOf()) private val noDataReceived = Collections.synchronizedSet(mutableSetOf()) private val noDataSend = Collections.synchronizedSet(mutableSetOf()) + private val nodesPaused = Collections.synchronizedSet(mutableSetOf()) val nodesConnected: Boolean get() = connectedNodes.size>0 private var connectRetries = 0 private val filter = mutableSetOf( @@ -78,17 +84,28 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC return batterLevels } - fun getNodeBatterLevels(addMissing: Boolean = true): Map { - val nodeBatterLevels = mutableMapOf() + private fun getDisplayName(node: Node): String { + val result = node.displayName.replace("_", " ") + return if(result.indexOf("(") > 0) { + result.take(result.indexOf("(")).trim() + } else { + result.trim() + } + } + + fun getNodeConnectionStates(context: Context, addMissing: Boolean = true): Map { + val connectionStates = mutableMapOf() connectedNodes.forEach { node -> (if (nodeBatteryLevel.containsKey(node.key)) { - nodeBatterLevels[node.value.displayName] = nodeBatteryLevel.getValue(node.key) + val level = nodeBatteryLevel.getValue(node.key) + connectionStates[getDisplayName(node.value)] = if (level > 0) "${level}%" else context.getString(R.string.state_connected) } else if (addMissing) { - nodeBatterLevels[node.value.displayName] = -1 + connectionStates[getDisplayName(node.value)] = context.getString(R.string.state_await_data) }) } - return nodeBatterLevels + return connectionStates + } } @@ -115,6 +132,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC nodeBatteryLevel.clear() noDataReceived.clear() noDataSend.clear() + nodesPaused.clear() Log.d(LOG_ID, "connection closed") } @@ -144,6 +162,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC private fun addTimer() { if(!filter.contains(NotifySource.TIME_VALUE)) { + Log.i(LOG_ID, "add timer") filter.add(NotifySource.TIME_VALUE) InternalNotifier.addNotifier(context, this, filter) } @@ -151,6 +170,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC private fun removeTimer() { if(filter.contains(NotifySource.TIME_VALUE)) { + Log.i(LOG_ID, "remove timer") filter.remove(NotifySource.TIME_VALUE) InternalNotifier.addNotifier(context, this, filter) } @@ -220,6 +240,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC } } } + InternalNotifier.notify(context, NotifySource.CAPILITY_INFO, null) } else if(forceSendDataRequest) sendDataRequest() } @@ -241,6 +262,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC nodeBatteryLevel.remove(nodeId)// remove all battery levels from not connected nodes noDataReceived.remove(nodeId) noDataSend.remove(nodeId) + nodesPaused.remove(nodeId) if (connectedNodes.isEmpty()) removeTimer() } @@ -268,7 +290,6 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC private fun sendDataRequest(filterReceiverId: String? = null) { val extras = ReceiveData.createExtras() - InternalNotifier.notify(context, NotifySource.CAPILITY_INFO, extras) extras?.putBundle(Constants.ALARM_EXTRA_BUNDLE, AlarmHandler.getExtras()) sendMessage(NotifySource.CAPILITY_INFO, extras, filterReceiverId = filterReceiverId) // send data request for new node } @@ -313,7 +334,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC Log.v(LOG_ID, "sendMessage called for $dataSource filter receiver $filterReceiverId ignoring receiver $ignoreReceiverId with extras $extras") if( nodesConnected && dataSource != NotifySource.NODE_BATTERY_LEVEL ) { Log.d(LOG_ID, connectedNodes.size.toString() + " nodes found for sending message for " + dataSource.toString()) - if (extras != null && dataSource != NotifySource.BATTERY_LEVEL && BatteryReceiver.batteryPercentage > 0) { + if (extras != null && dataSource != NotifySource.BATTERY_LEVEL && BatteryReceiver.batteryPercentage >= 0) { extras.putInt(BatteryReceiver.LEVEL, BatteryReceiver.batteryPercentage) } if (extras != null && dataSource == NotifySource.CAPILITY_INFO && GlucoDataService.appSource == AppSource.PHONE_APP) { @@ -337,6 +358,8 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC } }.start() } + if(dataSource == NotifySource.BROADCAST) + lastSendValuesTime = ReceiveData.time } } catch (cancellationException: CancellationException) { throw cancellationException @@ -362,6 +385,8 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC dataSource.toString() + " data send to node " + node.toString() ) noDataSend.remove(node.id) + if(notConnectedNodes.isEmpty()) + removeTimer() } addOnFailureListener { error -> if (retryCount < 2) { @@ -369,7 +394,13 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC LOG_ID, "Failed " + (retryCount+1).toString() + ". time to send " + dataSource.toString() + " data to node " + node.toString() + ": $error" ) - sendMessage(node, path, data, dataSource, retryCount+1) + Thread { + try { + sendMessage(node, path, data, dataSource, retryCount+1) + } catch (exc: Exception) { + Log.e(LOG_ID, "sendMessage to " + node.toString() + " exception: " + exc.toString()) + } + }.start() } else { Log.e( LOG_ID, @@ -394,6 +425,9 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC commandBundle.putBundle(Constants.SOURCE_SETTINGS_BUNDLE, DataSourceTask.getSettingsBundle(context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE))) commandBundle.putBundle(Constants.ALARM_SETTINGS_BUNDLE, AlarmHandler.getSettings()) } + if(BatteryReceiver.batteryPercentage >= 0) { + commandBundle.putInt(BatteryReceiver.LEVEL, BatteryReceiver.batteryPercentage) + } } connectedNodes.forEach { node -> Thread { @@ -411,6 +445,8 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC Log.i(LOG_ID, "onMessageReceived from " + p0.sourceNodeId + " with path " + p0.path) checkConnectedNode(p0.sourceNodeId) noDataReceived.remove(p0.sourceNodeId) + if (notConnectedNodes.isEmpty()) + removeTimer() val extras = Utils.bytesToBundle(p0.data) //Log.v(LOG_ID, "Received extras for path ${p0.path}: ${Utils.dumpBundle(extras)}") if(extras!= null) { @@ -457,7 +493,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC } if(p0.path == Constants.COMMAND_PATH) { - handleCommand(extras) + handleCommand(extras, p0.sourceNodeId) return } @@ -528,7 +564,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC Log.d(LOG_ID, "Data request received from " + p0.sourceNodeId) var bundle = ReceiveData.createExtras() var source = NotifySource.BROADCAST - if( bundle == null && BatteryReceiver.batteryPercentage > 0) { + if( bundle == null && BatteryReceiver.batteryPercentage >= 0) { bundle = BatteryReceiver.batteryBundle source = NotifySource.BATTERY_LEVEL } @@ -555,9 +591,9 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC } } - private fun handleCommand(extras: Bundle) { + private fun handleCommand(extras: Bundle, nodeId: String) { try { - Log.d(LOG_ID, "Command received: ${Utils.dumpBundle(extras)}") + Log.d(LOG_ID, "Command received from node $nodeId: ${Utils.dumpBundle(extras)}") val command = Command.valueOf(extras.getString(Constants.COMMAND_EXTRA, "")) val bundle = extras.getBundle(Constants.COMMAND_BUNDLE) when(command) { @@ -566,6 +602,25 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC Command.TEST_ALARM -> AlarmNotificationBase.instance!!.executeTest(AlarmType.fromIndex(bundle!!.getInt(Constants.ALARM_TYPE_EXTRA, ReceiveData.getAlarmType().ordinal)), context, false) Command.AA_CONNECTION_STATE -> InternalNotifier.notify(context, NotifySource.CAR_CONNECTION, bundle) Command.DISABLE_INACTIVE_TIME -> AlarmHandler.disableInactiveTime(fromClient = true) + Command.PAUSE_NODE -> nodesPaused.add(nodeId) + Command.RESUME_NODE -> { + if(nodesPaused.contains(nodeId)) { + Log.d(LOG_ID, "Resume node $nodeId") + nodesPaused.remove(nodeId) + if (canSendMessage(context, NotifySource.BROADCAST)) + sendMessage(NotifySource.BROADCAST, ReceiveData.createExtras()) + else + sendCommand(Command.FORCE_UPDATE, ReceiveData.createExtras()) + } + } + Command.FORCE_UPDATE -> { + if(ReceiveData.hasNewValue(bundle)) { + ReceiveData.handleIntent(context, dataSource, bundle, true) + } else { + Log.d(LOG_ID, "Force update received from node $nodeId") + InternalNotifier.notify(context, NotifySource.MESSAGECLIENT, bundle) + } + } } } catch (exc: Exception) { @@ -614,12 +669,42 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC } } + private fun canSendMessage(context: Context, dataSource: NotifySource): Boolean { + if(!nodesConnected) + return false + if(dataSource == NotifySource.BROADCAST && !ReceiveData.forceAlarm && ReceiveData.getAlarmType() != AlarmType.VERY_LOW) { + val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) + if(!sharedPref.getBoolean(Constants.SHARED_PREF_PHONE_WEAR_SCREEN_OFF_UPDATE, true) && nodesPaused.size == connectedNodes.size) { + Log.d(LOG_ID, "Ignore data because all nodes paused") + return false + } + if(lastSendValuesTime == ReceiveData.time) { + Log.d(LOG_ID, "Ignore data because of same time") + return false + } + val interval = sharedPref.getInt(Constants.SHARED_PREF_SEND_TO_WATCH_INTERVAL, 1) + val elapsedTime = Utils.getElapsedTimeMinute(lastSendValuesTime, RoundingMode.HALF_UP) + Log.v(LOG_ID, "Check sending for interval $interval - elapsed: ${elapsedTime}") + if (interval > 1 && elapsedTime < interval) { + Log.d(LOG_ID, "Ignore data because of interval $interval - elapsed: ${elapsedTime} - last: ${Utils.getUiTimeStamp(lastSendValuesTime)}") + return false + } + } else if (dataSource == NotifySource.BATTERY_LEVEL) { + val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) + if(!sharedPref.getBoolean(Constants.SHARED_PREF_PHONE_WEAR_SCREEN_OFF_UPDATE, true) && nodesPaused.size == connectedNodes.size) { + Log.d(LOG_ID, "Ignore data because all nodes paused") + return false + } + } + return true + } + override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) { try { Log.d(LOG_ID, "OnNotifyData called for " + dataSource.toString()) if (dataSource == NotifySource.TIME_VALUE) { checkNodesConnected() - } else if (extras != null || !ignoreSourceWithoutExtras(dataSource)) { + } else if ((extras != null || !ignoreSourceWithoutExtras(dataSource)) && canSendMessage(context, dataSource)) { Log.d(LOG_ID, "OnNotifyData for source " + dataSource.toString() + " and extras " + extras.toString()) sendMessage(dataSource, extras) } diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notification/AlarmHandler.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notification/AlarmHandler.kt index ebfecc4bf..21564476a 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/notification/AlarmHandler.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notification/AlarmHandler.kt @@ -6,9 +6,9 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.SharedPreferences -import android.os.Build import android.os.Bundle import android.util.Log +import de.michelinside.glucodatahandler.common.AppSource import de.michelinside.glucodatahandler.common.Command import de.michelinside.glucodatahandler.common.Constants import de.michelinside.glucodatahandler.common.GlucoDataService @@ -17,6 +17,7 @@ import de.michelinside.glucodatahandler.common.notification.AlarmSetting.Compani import de.michelinside.glucodatahandler.common.notifier.InternalNotifier import de.michelinside.glucodatahandler.common.notifier.NotifierInterface import de.michelinside.glucodatahandler.common.notifier.NotifySource +import de.michelinside.glucodatahandler.common.receiver.ScreenEventReceiver import de.michelinside.glucodatahandler.common.utils.Utils import java.text.DateFormat import java.time.Duration @@ -45,6 +46,7 @@ object AlarmHandler: SharedPreferences.OnSharedPreferenceChangeListener, Notifie private var inactiveStartTime = "" private var inactiveEndTime = "" private var inactiveWeekdays = defaultWeekdays + private var forceVeryLow = false private lateinit var sharedExtraPref: SharedPreferences private var alarmManager: AlarmManager? = null @@ -79,7 +81,9 @@ object AlarmHandler: SharedPreferences.OnSharedPreferenceChangeListener, Notifie return LocalTime.parse(inactiveEndTime, timeFormatter).format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)) } - private val isInactive: Boolean get() { + fun isInactive(alarmType: AlarmType) : Boolean { + if(forceVeryLow && alarmType == AlarmType.VERY_LOW) + return false // force very low alarm return isTempInactive || isSnoozeActive } @@ -109,6 +113,7 @@ object AlarmHandler: SharedPreferences.OnSharedPreferenceChangeListener, Notifie " - lastAlarmTime=" + DateFormat.getTimeInstance(DateFormat.SHORT).format(Date(lastAlarmTime)) + " - snoozeTime=" + (if(isSnoozeActive)snoozeTimestamp else "off") + " - tempInactive=" + (if(isTempInactive) inactiveEndTime else "off") + + " - forceVeryLow=" + forceVeryLow.toString() + " - time=" + DateFormat.getTimeInstance(DateFormat.SHORT).format(Date(ReceiveData.time)) + " - delta=" + ReceiveData.delta.toString() + " - rate=" + ReceiveData.rate.toString() + @@ -118,7 +123,7 @@ object AlarmHandler: SharedPreferences.OnSharedPreferenceChangeListener, Notifie " - high=>" + (if(AlarmType.HIGH.setting!!.isActive) DateFormat.getTimeInstance(DateFormat.SHORT).format(Date(lastAlarmTime+AlarmType.HIGH.setting.intervalMS)) else "off") + " - veryHigh=>" + (if(AlarmType.VERY_HIGH.setting!!.isActive) DateFormat.getTimeInstance(DateFormat.SHORT).format(Date(lastAlarmTime+AlarmType.VERY_HIGH.setting.intervalMS)) else "off") ) - if (isInactive) + if (isInactive(newAlarmType)) return false val triggerAlarm = when(newAlarmType) { @@ -159,17 +164,19 @@ object AlarmHandler: SharedPreferences.OnSharedPreferenceChangeListener, Notifie } fun setSnoozeTime(time: Long, fromClient: Boolean = false) { - snoozeTime = time - Log.i(LOG_ID, "New snooze-time: $snoozeTimestamp") - saveExtras() - if(GlucoDataService.context != null) { - InternalNotifier.notify(GlucoDataService.context!!, NotifySource.ALARM_STATE_CHANGED, null) - triggerSnoozeEnd(GlucoDataService.context!!) - } - if(!fromClient) { - val bundle = Bundle() - bundle.putLong(SNOOZE_TIME, snoozeTime) - GlucoDataService.sendCommand(Command.SNOOZE_ALARM, bundle) + if(snoozeTime != time) { + snoozeTime = time + Log.i(LOG_ID, "New snooze-time: $snoozeTimestamp") + saveExtras() + if(GlucoDataService.context != null) { + InternalNotifier.notify(GlucoDataService.context!!, NotifySource.ALARM_STATE_CHANGED, null) + triggerSnoozeEnd(GlucoDataService.context!!) + } + if(!fromClient) { + val bundle = Bundle() + bundle.putLong(SNOOZE_TIME, snoozeTime) + GlucoDataService.sendCommand(Command.SNOOZE_ALARM, bundle) + } } } @@ -188,9 +195,7 @@ object AlarmHandler: SharedPreferences.OnSharedPreferenceChangeListener, Notifie } private fun checkHighAlarm(newAlarmType: AlarmType, alarmInterval: Int): Boolean { - if(newAlarmType > lastAlarmType) - return true - if (ReceiveData.time - lastAlarmTime >= alarmInterval) { + if (newAlarmType > lastAlarmType || ReceiveData.time - lastAlarmTime >= alarmInterval) { if(ReceiveData.delta.isNaN() || ReceiveData.delta == 0F) return ReceiveData.rate > 0F return ReceiveData.delta > 0F @@ -199,9 +204,7 @@ object AlarmHandler: SharedPreferences.OnSharedPreferenceChangeListener, Notifie } private fun checkLowAlarm(newAlarmType: AlarmType, alarmInterval: Int): Boolean { - if(newAlarmType < lastAlarmType) - return true - if (ReceiveData.time - lastAlarmTime >= alarmInterval) { + if (newAlarmType < lastAlarmType || ReceiveData.time - lastAlarmTime >= alarmInterval) { if(ReceiveData.delta.isNaN() || ReceiveData.delta == 0F) return ReceiveData.rate < 0F return ReceiveData.delta < 0F @@ -221,7 +224,7 @@ object AlarmHandler: SharedPreferences.OnSharedPreferenceChangeListener, Notifie " - rising=>" + (if(AlarmType.RISING_FAST.setting!!.isActive) DateFormat.getTimeInstance(DateFormat.SHORT).format(Date(lastRisingAlarmTime+AlarmType.RISING_FAST.setting.intervalMS)) else "off") ) - if(isInactive) + if(isInactive(AlarmType.NONE)) return result if(AlarmType.FALLING_FAST.setting.isActive && deltaFallingCount >= AlarmType.FALLING_FAST.setting.deltaCount && ReceiveData.rawValue <= AlarmType.FALLING_FAST.setting.deltaBorder) { @@ -282,12 +285,13 @@ object AlarmHandler: SharedPreferences.OnSharedPreferenceChangeListener, Notifie fun isAlarmSettingToShare(key: String?): Boolean { when(key) { null -> return false - Constants.SHARED_PREF_ALARM_SNOOZE_ON_NOTIFICATION, + Constants.SHARED_PREF_ALARM_SNOOZE_NOTIFICATION_BUTTONS, Constants.SHARED_PREF_NO_ALARM_NOTIFICATION_AUTO_CONNECTED, Constants.SHARED_PREF_ALARM_INACTIVE_ENABLED, Constants.SHARED_PREF_ALARM_INACTIVE_START_TIME, Constants.SHARED_PREF_ALARM_INACTIVE_END_TIME, - Constants.SHARED_PREF_ALARM_INACTIVE_WEEKDAYS -> return true + Constants.SHARED_PREF_ALARM_INACTIVE_WEEKDAYS, + Constants.SHARED_PREF_ALARM_FORCE_VERY_LOW -> return true else -> { AlarmType.entries.forEach { if (it.setting != null && it.setting.isAlarmSettingToShare(key)) @@ -348,6 +352,7 @@ object AlarmHandler: SharedPreferences.OnSharedPreferenceChangeListener, Notifie bundle.putString(Constants.SHARED_PREF_ALARM_INACTIVE_START_TIME, inactiveStartTime) bundle.putString(Constants.SHARED_PREF_ALARM_INACTIVE_END_TIME, inactiveEndTime) bundle.putStringArray(Constants.SHARED_PREF_ALARM_INACTIVE_WEEKDAYS, inactiveWeekdays.toTypedArray()) + bundle.putBoolean(Constants.SHARED_PREF_ALARM_FORCE_VERY_LOW, forceVeryLow) return bundle } @@ -367,6 +372,7 @@ object AlarmHandler: SharedPreferences.OnSharedPreferenceChangeListener, Notifie putString(Constants.SHARED_PREF_ALARM_INACTIVE_START_TIME, bundle.getString(Constants.SHARED_PREF_ALARM_INACTIVE_START_TIME, inactiveStartTime)) putString(Constants.SHARED_PREF_ALARM_INACTIVE_END_TIME, bundle.getString(Constants.SHARED_PREF_ALARM_INACTIVE_END_TIME, inactiveEndTime)) putStringSet(Constants.SHARED_PREF_ALARM_INACTIVE_WEEKDAYS, bundle.getStringArray(Constants.SHARED_PREF_ALARM_INACTIVE_WEEKDAYS)?.toMutableSet() ?: defaultWeekdays) + putBoolean(Constants.SHARED_PREF_ALARM_FORCE_VERY_LOW, bundle.getBoolean(Constants.SHARED_PREF_ALARM_FORCE_VERY_LOW, forceVeryLow)) } apply() } @@ -381,12 +387,14 @@ object AlarmHandler: SharedPreferences.OnSharedPreferenceChangeListener, Notifie readSettings(sharedPref, Constants.SHARED_PREF_ALARM_INACTIVE_START_TIME) readSettings(sharedPref, Constants.SHARED_PREF_ALARM_INACTIVE_END_TIME) readSettings(sharedPref, Constants.SHARED_PREF_ALARM_INACTIVE_WEEKDAYS) + readSettings(sharedPref, Constants.SHARED_PREF_ALARM_FORCE_VERY_LOW) } else { when(key) { Constants.SHARED_PREF_ALARM_INACTIVE_ENABLED -> inactiveEnabled = sharedPref.getBoolean(Constants.SHARED_PREF_ALARM_INACTIVE_ENABLED, inactiveEnabled) Constants.SHARED_PREF_ALARM_INACTIVE_START_TIME -> inactiveStartTime = sharedPref.getString(Constants.SHARED_PREF_ALARM_INACTIVE_START_TIME, inactiveStartTime) ?: "" Constants.SHARED_PREF_ALARM_INACTIVE_END_TIME -> inactiveEndTime = sharedPref.getString(Constants.SHARED_PREF_ALARM_INACTIVE_END_TIME, inactiveEndTime) ?: "" Constants.SHARED_PREF_ALARM_INACTIVE_WEEKDAYS -> inactiveWeekdays = sharedPref.getStringSet(Constants.SHARED_PREF_ALARM_INACTIVE_WEEKDAYS, defaultWeekdays) ?: defaultWeekdays + Constants.SHARED_PREF_ALARM_FORCE_VERY_LOW -> forceVeryLow = sharedPref.getBoolean(Constants.SHARED_PREF_ALARM_FORCE_VERY_LOW, forceVeryLow) } } } @@ -403,10 +411,27 @@ object AlarmHandler: SharedPreferences.OnSharedPreferenceChangeListener, Notifie checkNotifier(context) } - private fun checkNotifier(context: Context) { - Log.v(LOG_ID, "checkNotifier called") - if(AlarmType.OBSOLETE.setting!!.isActive != InternalNotifier.hasNotifier(this)) { - if(AlarmType.OBSOLETE.setting.isActive) { + private fun isObsoleteAlarmActive(): Boolean { + if(GlucoDataService.appSource != AppSource.WEAR_APP) + return AlarmType.OBSOLETE.setting!!.isActive + if(AlarmType.OBSOLETE.setting!!.isActive) { // on wear: only notification is used for alarms -> check to save battery for timer thread + if(AlarmNotificationBase.instance?.getAlarmState(GlucoDataService.context!!) == AlarmState.ACTIVE) { + if(ScreenEventReceiver.isDisplayOff()) { + // if display is off and the phone does not send new data to wear, the obsolete alarm should not trigger! + if(GlucoDataService.sharedPref != null) + return GlucoDataService.sharedPref!!.getBoolean(Constants.SHARED_PREF_PHONE_WEAR_SCREEN_OFF_UPDATE, true) + } + return true + } + } + return false + } + + fun checkNotifier(context: Context) { + val obsoleteActive = isObsoleteAlarmActive() + Log.v(LOG_ID, "checkNotifier called - obsoleteActive=$obsoleteActive") + if(obsoleteActive != InternalNotifier.hasNotifier(this)) { + if(obsoleteActive) { InternalNotifier.addNotifier(context, this, mutableSetOf(NotifySource.TIME_VALUE)) } else { InternalNotifier.remNotifier(context, this) @@ -455,11 +480,9 @@ object AlarmHandler: SharedPreferences.OnSharedPreferenceChangeListener, Notifie if(isSnoozeActive) { alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager var hasExactAlarmPermission = true - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if (!alarmManager!!.canScheduleExactAlarms()) { - Log.d(LOG_ID, "Need permission to set exact alarm!") - hasExactAlarmPermission = false - } + if (!Utils.canScheduleExactAlarms(context)) { + Log.d(LOG_ID, "Need permission to set exact alarm!") + hasExactAlarmPermission = false } val intent = Intent(context, AlarmSnoozeEndReceiver::class.java) intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notification/AlarmNotificationBase.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notification/AlarmNotificationBase.kt index 982759c9b..565626039 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/notification/AlarmNotificationBase.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notification/AlarmNotificationBase.kt @@ -37,7 +37,6 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha protected val LOG_ID = "GDH.AlarmNotification" private val MIN_AUTO_CLOSE_DELAY = 30F private var enabled: Boolean = false - private var addSnooze: Boolean = false private val VERY_LOW_NOTIFICATION_ID = 801 private val LOW_NOTIFICATION_ID = 802 private val HIGH_NOTIFICATION_ID = 803 @@ -62,11 +61,13 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha private var alarmManager: AlarmManager? = null private var alarmPendingIntent: PendingIntent? = null private var useAlarmSound: Boolean = true + val useAlarmStream: Boolean get() = useAlarmSound private var autoCloseNotification: Boolean = false private var currentAlarmState: AlarmState = AlarmState.DISABLED private var startDelayThread: Thread? = null private var checkSoundThread: Thread? = null private var checkNotificationThread: Thread? = null + private var snoozeNotificationButtons: MutableSet = mutableSetOf() enum class TriggerAction { TEST_ALARM, @@ -103,7 +104,7 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha return curNotification > 0 } - fun getAlarmState(context: Context): AlarmState { + fun getAlarmState(context: Context, alarmType: AlarmType = AlarmType.NONE): AlarmState { var state = AlarmState.currentState(context) if(state == AlarmState.DISABLED || !channelActive(context)) { state = AlarmState.DISABLED @@ -115,6 +116,11 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha ) state = AlarmState.INACTIVE } + } else if(state == AlarmState.TEMP_DISABLED || state == AlarmState.SNOOZE) { + if(!AlarmHandler.isInactive(alarmType)) { + Log.i(LOG_ID, "Force $alarmType") + state = AlarmState.ACTIVE // force alarm + } } if(currentAlarmState != state) { Log.i(LOG_ID, "Current alarm state: $state - last state: $currentAlarmState") @@ -149,7 +155,7 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha filter.add(NotifySource.OBSOLETE_ALARM_TRIGGER) filter.add(NotifySource.DELTA_ALARM_TRIGGER) filter.addAll(getNotifierFilter()) - InternalNotifier.addNotifier(context, this, filter ) + InternalNotifier.addNotifier(context, this, filter) } fun getEnabled(): Boolean = enabled @@ -169,15 +175,19 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha } } - fun getAddSnooze(): Boolean = addSnooze + fun getAddSnooze(): Boolean = !snoozeNotificationButtons.isEmpty() - private fun setAddSnooze(snooze: Boolean) { - try { - Log.v(LOG_ID, "setAddSnooze called: current=$addSnooze - new=$snooze") - addSnooze = snooze - } catch (exc: Exception) { - Log.e(LOG_ID, "setAddSnooze exception: " + exc.toString() ) - } + private fun setSnoozeNotificationButtons(buttons: MutableSet) { + snoozeNotificationButtons = buttons.toMutableSet() + Log.d(LOG_ID, "Snooze Buttons: $snoozeNotificationButtons") + } + + private fun getSnoozeNotificationButtons(): Set { + return snoozeNotificationButtons.toSet() + } + + fun getSnoozeValues(): List { + return getSnoozeNotificationButtons().map { it.toLong() }.sorted().take(3) } fun destroy(context: Context) { @@ -196,6 +206,7 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha if (curNotification > 0) { stopNotification(curNotification, context, fromClient = fromClient) } else { + stopVibrationAndSound() // force stop! onNotificationStopped(0, context) } } @@ -228,10 +239,17 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha } } + fun stopForLockscreenSnooze() { + Log.d(LOG_ID, "stopForLockscreenSnooze called") + stopVibrationAndSound() + stopTrigger() + GlucoDataService.sendCommand(Command.STOP_ALARM) + } fun stopVibrationAndSound() { try { Log.d(LOG_ID, "stopVibrationAndSound called") + stopDelayThread() stopSoundThread() Vibrator.cancel() ringtoneRWLock.write { @@ -248,7 +266,7 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha private fun triggerNotification(alarmType: AlarmType, context: Context, forTest: Boolean = false) { try { Log.d(LOG_ID, "triggerNotification called for $alarmType - active=$active - curNotification=$curNotification - forTest=$forTest") - if (getAlarmState(context) == AlarmState.ACTIVE || forTest) { + if (getAlarmState(context, alarmType) == AlarmState.ACTIVE || forTest) { stopCurrentNotification(context, true) // do not send stop to client! -> to prevent, that the client will stop the newly created notification! curNotification = getNotificationId(alarmType) retriggerCount = 0 @@ -260,10 +278,9 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha else AlarmType.NONE Log.i(LOG_ID, "Create notification for $alarmType with ID=$curNotification - triggerTime=$retriggerTime") - checkCreateSound(alarmType, context) if(canShowNotification()) showNotification(alarmType, context) - startVibrationAndSound(alarmType, context) + triggerVibrationAndSound(alarmType, context) } } catch (exc: Exception) { Log.e(LOG_ID, "triggerNotification exception: " + exc.toString() + "\n" + exc.stackTraceToString() ) @@ -280,11 +297,9 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha Log.i(LOG_ID, "Trigger action $action for $alarmType in $delaySeconds seconds") alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager var hasExactAlarmPermission = true - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if(!alarmManager!!.canScheduleExactAlarms()) { - Log.d(LOG_ID, "Need permission to set exact alarm!") - hasExactAlarmPermission = false - } + if (!Utils.canScheduleExactAlarms(context)) { + Log.d(LOG_ID, "Need permission to set exact alarm!") + hasExactAlarmPermission = false } val intent = Intent(context, AlarmIntentReceiver::class.java) intent.action = action.toString() @@ -348,6 +363,8 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha Channels.getNotificationManager(context).deleteNotificationChannel("gdh_alarm_notification_silence_channel") Channels.getNotificationManager(context).deleteNotificationChannel("gdh_alarm_notification_channel") Channels.getNotificationManager(context).deleteNotificationChannel("gdh_alarm_notification_sound") + Channels.getNotificationManager(context).deleteNotificationChannel("gdh_alarm_notification_channel_66") + Channels.getNotificationManager(context).deleteNotificationChannel("gdh_alarm_notification_channel_67") } protected fun createSnoozeIntent(context: Context, snoozeTime: Long, noticationId: Int): PendingIntent { @@ -406,10 +423,10 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha val extender = Notification.WearableExtender() extender.addAction(createStopAction(context, context.resources.getString(CR.string.btn_dismiss), getNotificationId(alarmType))) if (getAddSnooze()) { - extender - .addAction(createSnoozeAction(context, context.getString(CR.string.snooze) + ": 60", 60L, getNotificationId(alarmType))) - .addAction(createSnoozeAction(context, context.getString(CR.string.snooze) + ": 90", 90L, getNotificationId(alarmType))) - .addAction(createSnoozeAction(context, context.getString(CR.string.snooze) + ": 120", 120L, getNotificationId(alarmType))) + getSnoozeValues().forEach { + extender.addAction(createSnoozeAction(context, context.getString(CR.string.snooze) + ": $it", it, getNotificationId(alarmType))) + + } } notificationBuilder.extend(extender) @@ -417,7 +434,48 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha return notificationBuilder.build() } + private fun triggerVibrationAndSound(alarmType: AlarmType, context: Context) { + if(startDelayThread!=null && startDelayThread!!.isAlive) { + Log.w(LOG_ID, "Start sound thread is already running!") + return + } + if(curNotification > 0) { + // else + startDelayThread = Thread { + try { + val startDelay = getStartDelayMs(context) + Log.i(LOG_ID, "Start sound and vibration with a delay of $startDelay ms") + if (startDelay > 0) + Thread.sleep(startDelay.toLong()) + if (curNotification > 0) { + checkCreateSound(alarmType, context) + startVibrationAndSound(alarmType, context) + } + } catch (exc: InterruptedException) { + Log.d(LOG_ID, "Delay thread interrupted") + } catch (exc: Exception) { + Log.e(LOG_ID, "Exception in delay thread: " + exc.toString()) + } + } + startDelayThread!!.start() + } + } + + private fun stopDelayThread() { + Log.v(LOG_ID, "Stop delay thread for $startDelayThread") + if (startDelayThread != null && startDelayThread!!.isAlive && startDelayThread!!.id != Thread.currentThread().id ) + { + Log.i(LOG_ID, "Stop running delay thread!") + startDelayThread!!.interrupt() + while(startDelayThread!!.isAlive) + Thread.sleep(1) + Log.i(LOG_ID, "Delay thread stopped!") + startDelayThread = null + } + } + private fun startVibrationAndSound(alarmType: AlarmType, context: Context, reTrigger: Boolean = false) { + Log.d(LOG_ID, "Start sound and vibration for $alarmType - reTrigger=$reTrigger") val repeat = getRepeat(alarmType) if(!reTrigger) { val soundDelay = getSoundDelay(alarmType) @@ -438,26 +496,8 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha } } - if(startDelayThread!=null && startDelayThread!!.isAlive) { - Log.w(LOG_ID, "Start sound thread is already running!") - return - } - - if(curNotification > 0) { - // else - startDelayThread = Thread { - val startDelay = getStartDelayMs(context) - Log.i(LOG_ID, "Start sound and vibration with a delay of $startDelay ms") - if (startDelay > 0) - Thread.sleep(startDelay.toLong()) - if (curNotification > 0) { - Log.d(LOG_ID, "Start sound and vibration") - val duration = startSound(alarmType, context, true) - checkRetriggerAndAutoClose(context, duration) - } - } - startDelayThread!!.start() - } + val duration = startSound(alarmType, context, true) + checkRetriggerAndAutoClose(context, duration) } private fun vibrate(alarmType: AlarmType, repeat: Boolean = false, forceReturnDuration: Boolean = false) : Int { @@ -467,7 +507,8 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha val duration = if(repeat && !forceReturnDuration) -1 else vibratePattern.sum().toInt() Log.i(LOG_ID, "start vibration for $alarmType - repeat: $repeat - duration: $duration ms") Vibrator.vibrate(vibratePattern, if(repeat) 1 else -1, - alarmType.setting?.vibrateAmplitude ?: -1 + alarmType.setting?.vibrateAmplitude ?: -1, + useAlarmSound ) return duration } @@ -575,12 +616,16 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha return false } - fun forceDnd(): Boolean { - if(!Channels.getNotificationManager().isNotificationPolicyAccessGranted && - ( Channels.getNotificationManager().currentInterruptionFilter > NotificationManager.INTERRUPTION_FILTER_ALL || - audioManager.ringerMode == AudioManager.RINGER_MODE_SILENT) ) { - Log.d(LOG_ID, "Force DnD is active") - return true + private fun forceDnd(): Boolean { // return true, if DnD is enabled and should not be overwritten + if(Channels.getNotificationManager().currentInterruptionFilter > NotificationManager.INTERRUPTION_FILTER_ALL || + audioManager.ringerMode == AudioManager.RINGER_MODE_SILENT) { + if(!forceSound && !forceVibration) { + return true + } + if(!Channels.getNotificationManager().isNotificationPolicyAccessGranted) { + Log.i(LOG_ID, "Access DnD not granted!") + return true + } } return false } @@ -686,7 +731,14 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha } if(lastRingerMode >= 0 ) { Log.i(LOG_ID, "Reset ringer mode to $lastRingerMode") + if(Build.VERSION.SDK_INT > Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + // fix for Android 15 to recreate silent mode after vibrate mode + if(audioManager.ringerMode == AudioManager.RINGER_MODE_VIBRATE && lastRingerMode == AudioManager.RINGER_MODE_SILENT) + audioManager.ringerMode = AudioManager.RINGER_MODE_NORMAL + } audioManager.ringerMode = lastRingerMode + // fix for Android 15 to not activate DnD for silent mode + Channels.getNotificationManager().setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL) lastRingerMode = -1 } if(lastDndMode != NotificationManager.INTERRUPTION_FILTER_UNKNOWN) { @@ -783,9 +835,9 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha } private fun getTriggerTime(alarmType: AlarmType): Int { - if (alarmType.setting!=null) { + /*if (alarmType.setting!=null) { return alarmType.setting.retriggerTime - } + }*/ return 0 } @@ -797,9 +849,14 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha } private fun getRepeat(alarmType: AlarmType): Int { - /*if (alarmType.setting!=null) { - return alarmType.setting.repeatTime - }*/ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + if (alarmType.setting!=null && alarmType.setting.repeatUntilClose) { + if (alarmType.setting.repeatTime > 0) { + return alarmType.setting.repeatTime + } + return -1 // unlimited + } + } return 0 } @@ -821,7 +878,7 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha Log.d(LOG_ID, "onSharedPreferenceChanged called for " + key) if (key == null) { onSharedPreferenceChanged(sharedPreferences, Constants.SHARED_PREF_ALARM_NOTIFICATION_ENABLED) - onSharedPreferenceChanged(sharedPreferences, Constants.SHARED_PREF_ALARM_SNOOZE_ON_NOTIFICATION) + onSharedPreferenceChanged(sharedPreferences, Constants.SHARED_PREF_ALARM_SNOOZE_NOTIFICATION_BUTTONS) onSharedPreferenceChanged(sharedPreferences, Constants.SHARED_PREF_ALARM_FORCE_SOUND) onSharedPreferenceChanged(sharedPreferences, Constants.SHARED_PREF_ALARM_FORCE_VIBRATION) onSharedPreferenceChanged(sharedPreferences, Constants.SHARED_PREF_NOTIFICATION_VIBRATE) @@ -830,7 +887,7 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha } else { when(key) { Constants.SHARED_PREF_ALARM_NOTIFICATION_ENABLED -> setEnabled(sharedPreferences.getBoolean(Constants.SHARED_PREF_ALARM_NOTIFICATION_ENABLED, enabled)) - Constants.SHARED_PREF_ALARM_SNOOZE_ON_NOTIFICATION -> setAddSnooze(sharedPreferences.getBoolean(Constants.SHARED_PREF_ALARM_SNOOZE_ON_NOTIFICATION, addSnooze)) + Constants.SHARED_PREF_ALARM_SNOOZE_NOTIFICATION_BUTTONS -> setSnoozeNotificationButtons(sharedPreferences.getStringSet(Constants.SHARED_PREF_ALARM_SNOOZE_NOTIFICATION_BUTTONS, snoozeNotificationButtons) as MutableSet) Constants.SHARED_PREF_ALARM_FORCE_SOUND -> forceSound = sharedPreferences.getBoolean(Constants.SHARED_PREF_ALARM_FORCE_SOUND, forceSound) Constants.SHARED_PREF_ALARM_FORCE_VIBRATION -> forceVibration = sharedPreferences.getBoolean(Constants.SHARED_PREF_ALARM_FORCE_VIBRATION, forceVibration) Constants.SHARED_PREF_NOTIFICATION_VIBRATE -> vibrateOnly = sharedPreferences.getBoolean(Constants.SHARED_PREF_NOTIFICATION_VIBRATE, vibrateOnly) @@ -847,7 +904,7 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha fun getSettings(): Bundle { val bundle = Bundle() - bundle.putBoolean(Constants.SHARED_PREF_ALARM_SNOOZE_ON_NOTIFICATION, getAddSnooze()) + bundle.putStringArray(Constants.SHARED_PREF_ALARM_SNOOZE_NOTIFICATION_BUTTONS, getSnoozeNotificationButtons().toTypedArray()) if(GlucoDataService.sharedPref != null) { bundle.putBoolean(Constants.SHARED_PREF_NO_ALARM_NOTIFICATION_AUTO_CONNECTED, GlucoDataService.sharedPref!!.getBoolean(Constants.SHARED_PREF_NO_ALARM_NOTIFICATION_AUTO_CONNECTED, false)) } @@ -855,8 +912,8 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha } fun saveSettings(bundle: Bundle, editor: Editor) { - if(bundle.containsKey(Constants.SHARED_PREF_ALARM_SNOOZE_ON_NOTIFICATION)) { - editor.putBoolean(Constants.SHARED_PREF_ALARM_SNOOZE_ON_NOTIFICATION, bundle.getBoolean(Constants.SHARED_PREF_ALARM_SNOOZE_ON_NOTIFICATION, getAddSnooze())) + if(bundle.containsKey(Constants.SHARED_PREF_ALARM_SNOOZE_NOTIFICATION_BUTTONS)) { + editor.putStringSet(Constants.SHARED_PREF_ALARM_SNOOZE_NOTIFICATION_BUTTONS, bundle.getStringArray(Constants.SHARED_PREF_ALARM_SNOOZE_NOTIFICATION_BUTTONS)?.toMutableSet()?: getSnoozeNotificationButtons()) if(bundle.containsKey(Constants.SHARED_PREF_NO_ALARM_NOTIFICATION_AUTO_CONNECTED)) { editor.putBoolean(Constants.SHARED_PREF_NO_ALARM_NOTIFICATION_AUTO_CONNECTED, bundle.getBoolean(Constants.SHARED_PREF_NO_ALARM_NOTIFICATION_AUTO_CONNECTED)) } @@ -884,7 +941,9 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha } } NotifySource.ALARM_STATE_CHANGED -> { - getAlarmState(context) + if (getAlarmState(context) != AlarmState.ACTIVE) { + stopCurrentNotification(context) + } } else -> Log.w(LOG_ID, "Unsupported source $dataSource") } @@ -908,7 +967,7 @@ abstract class AlarmNotificationBase: NotifierInterface, SharedPreferences.OnSha return false } if(isTriggerActive()) { - Log.i(LOG_ID, "Retrigger sound after $retriggerTime minute(s) + $soundDuration ms") + Log.i(LOG_ID, "Retrigger sound after $retriggerTime minute(s) + $soundDuration ms - count $retriggerCount") retriggerCount++ triggerDelay(TriggerAction.RETRIGGER_SOUND, getAlarmType(), context, (retriggerTime*60).toFloat() + (soundDuration.toFloat()/1000)) return true diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notification/AlarmSetting.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notification/AlarmSetting.kt index a4fb06dac..a5171b90c 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/notification/AlarmSetting.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notification/AlarmSetting.kt @@ -28,8 +28,8 @@ open class AlarmSetting(val alarmPrefix: String, var intervalMin: Int) { var vibratePatternKey = alarmPrefix private var vibrateAmplitudePref = 15 var soundDelay = 0 - var retriggerTime = 0 - //var repeatTime = 0 + var repeatUntilClose = false + var repeatTime = 0 // not for sharing with watch! var soundLevel = -1 var useCustomSound = false @@ -78,12 +78,12 @@ open class AlarmSetting(val alarmPrefix: String, var intervalMin: Int) { getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_ENABLED), getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_INTERVAL), getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_SOUND_DELAY), - getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_RETRIGGER), getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_INACTIVE_ENABLED), getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_INACTIVE_START_TIME), getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_INACTIVE_END_TIME), getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_INACTIVE_WEEKDAYS), - getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT) + getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT), + getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT_UNTIL_CLOSE) ) open fun getPreferencesToShare(): MutableSet { @@ -117,8 +117,8 @@ open class AlarmSetting(val alarmPrefix: String, var intervalMin: Int) { bundle.putString(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_INACTIVE_END_TIME), inactiveEndTime) bundle.putStringArray(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_INACTIVE_WEEKDAYS), inactiveWeekdays.toTypedArray()) bundle.putInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_SOUND_DELAY), soundDelay) - bundle.putInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_RETRIGGER), retriggerTime) - //bundle.putInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT), repeatTime) + bundle.putBoolean(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT_UNTIL_CLOSE), repeatUntilClose) + bundle.putInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT), repeatTime) } if (hasDelta()) { bundle.putFloat(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_DELTA), delta) @@ -138,8 +138,8 @@ open class AlarmSetting(val alarmPrefix: String, var intervalMin: Int) { editor.putString(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_INACTIVE_END_TIME), bundle.getString(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_INACTIVE_END_TIME), inactiveEndTime)) editor.putStringSet(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_INACTIVE_WEEKDAYS), bundle.getStringArray(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_INACTIVE_WEEKDAYS))?.toMutableSet() ?: defaultWeekdays) editor.putInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_SOUND_DELAY), bundle.getInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_SOUND_DELAY), soundDelay)) - editor.putInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_RETRIGGER), bundle.getInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_RETRIGGER), retriggerTime)) - //editor.putInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT), bundle.getInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT), repeatTime)) + editor.putBoolean(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT_UNTIL_CLOSE), bundle.getBoolean(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT_UNTIL_CLOSE), repeatUntilClose)) + editor.putInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT), bundle.getInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT), repeatTime)) if (hasDelta()) { editor.putFloat(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_DELTA), bundle.getFloat(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_DELTA), 5F)) editor.putInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_OCCURRENCE_COUNT), bundle.getInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_OCCURRENCE_COUNT), 1)) @@ -160,11 +160,11 @@ open class AlarmSetting(val alarmPrefix: String, var intervalMin: Int) { inactiveEndTime = sharedPref.getString(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_INACTIVE_END_TIME), null) ?: "" inactiveWeekdays = sharedPref.getStringSet(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_INACTIVE_WEEKDAYS), defaultWeekdays) ?: defaultWeekdays soundDelay = sharedPref.getInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_SOUND_DELAY), soundDelay) - retriggerTime = sharedPref.getInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_RETRIGGER), retriggerTime) soundLevel = sharedPref.getInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_SOUND_LEVEL), soundLevel) useCustomSound = sharedPref.getBoolean(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_USE_CUSTOM_SOUND), useCustomSound) customSoundPath = sharedPref.getString(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_CUSTOM_SOUND), null) ?: "" - //repeatTime = sharedPref.getInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT), repeatTime) + repeatUntilClose = sharedPref.getBoolean(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT_UNTIL_CLOSE), repeatUntilClose) + repeatTime = sharedPref.getInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT), repeatTime) vibratePatternKey = sharedPref.getString(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_VIBRATE_PATTERN), alarmPrefix) ?: alarmPrefix vibrateAmplitudePref = sharedPref.getInt(getSettingName(Constants.SHARED_PREF_ALARM_SUFFIX_VIBRATE_AMPLITUDE), vibrateAmplitudePref) if(hasDelta()) { @@ -179,8 +179,8 @@ open class AlarmSetting(val alarmPrefix: String, var intervalMin: Int) { "inactiveStartTime=$inactiveStartTime, " + "inactiveEndTime=$inactiveEndTime, " + "soundDelay=$soundDelay, " + - //"repeatTime=$repeatTime, " + - "retriggerTime=$retriggerTime, " + + "repeatUntilClose=$repeatUntilClose, " + + "repeatTime=$repeatTime, " + "soundLevel=$soundLevel, " + "useCustomSound=$useCustomSound, " + "customSoundPath=$customSoundPath, " + diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notification/Channels.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notification/Channels.kt index c44c9aee9..87db96dcb 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/notification/Channels.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notification/Channels.kt @@ -19,7 +19,7 @@ enum class ChannelType(val channelId: String, val nameResId: Int, val descrResId WEAR_FOREGROUND("glucodatahandler_service_01", R.string.wear_foreground_notification_name, R.string.wear_foreground_notification_descr, NotificationManager.IMPORTANCE_LOW), ANDROID_AUTO("GlucoDataNotify_Car", R.string.android_auto_notification_name, R.string.android_auto_notification_descr ), ANDROID_AUTO_FOREGROUND("GlucoDataAuto_foreground", R.string.mobile_foreground_notification_name, R.string.mobile_foreground_notification_descr, NotificationManager.IMPORTANCE_LOW ), - ALARM("gdh_alarm_notification_channel_66", R.string.alarm_notification_name, R.string.alarm_notification_descr, NotificationManager.IMPORTANCE_MAX ); + ALARM("gdh_alarm_notification_channel_68", R.string.alarm_notification_name, R.string.alarm_notification_descr, NotificationManager.IMPORTANCE_MAX ); } object Channels { private val LOG_ID = "GDH.Channels" diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notification/Vibrator.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notification/Vibrator.kt index 2b2a2edb4..f1c87401f 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/notification/Vibrator.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notification/Vibrator.kt @@ -1,35 +1,48 @@ package de.michelinside.glucodatahandler.common.notification import android.content.Context +import android.media.AudioAttributes import android.os.Build +import android.os.CombinedVibration +import android.os.VibrationAttributes import android.os.VibrationEffect import android.os.Vibrator import android.os.VibratorManager import android.util.Log +import androidx.annotation.RequiresApi import de.michelinside.glucodatahandler.common.GlucoDataService object Vibrator { private val LOG_ID = "GDH.Vibrator" private var vibratorInstance: Vibrator? = null - val vibrator: Vibrator + + @get:RequiresApi(Build.VERSION_CODES.S) + private val vibratorManager: VibratorManager? + get() { + if(GlucoDataService.context != null) + return GlucoDataService.context!!.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager + return null + } + + private val vibrator: Vibrator? get() { - if(vibratorInstance == null) { + if(vibratorInstance == null && GlucoDataService.context != null) { vibratorInstance = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val manager = GlucoDataService.context!!.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager - manager.defaultVibrator + vibratorManager?.defaultVibrator } else { @Suppress("DEPRECATION") GlucoDataService.context!!.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator } } - return vibratorInstance!! + return vibratorInstance } - fun vibrate(pattern: LongArray, repeat: Int = -1, amplitude: Int = -1): Int { - cancel() - val duration = if(repeat == -1) pattern.sum().toInt() else -1 - if (vibrator.hasAmplitudeControl() && amplitude > 0) { - Log.d(LOG_ID, "Vibrate for $duration ms with amplitude $amplitude") + fun hasAmplitudeControl(): Boolean { + return vibrator?.hasAmplitudeControl() == true + } + + private fun getEffect(pattern: LongArray, repeat: Int = -1, amplitude: Int = -1): VibrationEffect { + if(hasAmplitudeControl()) { val amplitudePatterns = IntArray(pattern.size) amplitudePatterns[0] = 0 for(i in 1 until pattern.size) { @@ -38,16 +51,37 @@ object Vibrator { else amplitudePatterns[i] = 0 } - vibrator.vibrate(VibrationEffect.createWaveform(pattern, amplitudePatterns, repeat)) + return VibrationEffect.createWaveform(pattern, amplitudePatterns, repeat) + } + return VibrationEffect.createWaveform(pattern, repeat) + } + + @Suppress("DEPRECATION") + fun vibrate(pattern: LongArray, repeat: Int = -1, amplitude: Int = -1, useAlarm: Boolean = true): Int { + cancel() + val duration = if(repeat == -1) pattern.sum().toInt() else -1 + Log.d(LOG_ID, "Vibrate for $duration ms - useAlarm: $useAlarm") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val attributes = VibrationAttributes.Builder() + .setUsage(if(useAlarm)VibrationAttributes.USAGE_ALARM else VibrationAttributes.USAGE_NOTIFICATION) + .build() + vibratorManager?.vibrate(CombinedVibration.createParallel(getEffect(pattern, repeat, amplitude)),attributes) } else { - Log.d(LOG_ID, "Vibrate for $duration ms") - vibrator.vibrate(VibrationEffect.createWaveform(pattern, repeat)) + val aa = AudioAttributes.Builder() + .setUsage(if (useAlarm) AudioAttributes.USAGE_ALARM else AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build() + vibrator?.vibrate(getEffect(pattern, repeat, amplitude), aa) } return duration } fun cancel() { Log.d(LOG_ID, "Stop vibration") - vibrator.cancel() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + vibratorManager?.cancel() + } else { + vibrator?.cancel() + } } } \ No newline at end of file diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/DataSource.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/DataSource.kt index 1f856d861..11a41287c 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/DataSource.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/DataSource.kt @@ -1,8 +1,9 @@ package de.michelinside.glucodatahandler.common.notifier +import de.michelinside.glucodatahandler.common.Constants import de.michelinside.glucodatahandler.common.R -enum class DataSource(val resId: Int) { +enum class DataSource(val resId: Int, val interval5Min: Boolean = false) { NONE(R.string.empty_string), JUGGLUCO(R.string.source_juggluco), XDRIP(R.string.source_xdrip), @@ -11,10 +12,10 @@ enum class DataSource(val resId: Int) { LIBRELINK(R.string.source_libreview), NIGHTSCOUT(R.string.source_nightscout), AAPS(R.string.source_aaps), - GDH(R.string.source_gdh), - DEXCOM_SHARE(R.string.source_dexcom_share), - DEXCOM_BYODA(R.string.source_dexcom_byoda), - NS_EMULATOR(R.string.source_ns_emulator), + GDH(if(Constants.IS_SECOND) R.string.source_gdh_second else R.string.source_gdh), + DEXCOM_SHARE(R.string.source_dexcom_share, true), + DEXCOM_BYODA(R.string.source_dexcom_byoda, true), + NS_EMULATOR(R.string.source_ns_emulator, true), DIABOX(R.string.source_diabox); companion object { diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt index d9333870e..e780e44fd 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt @@ -18,7 +18,7 @@ object InternalNotifier { val timeChanged = hasSource(notifier, NotifySource.TIME_VALUE) != sourceFilter.contains(NotifySource.TIME_VALUE) || hasSource(notifier, NotifySource.OBSOLETE_VALUE) != sourceFilter.contains(NotifySource.OBSOLETE_VALUE) Log.i(LOG_ID, "add notifier $notifier - filter: $sourceFilter - timechanged: $timeChanged") - notifiers[notifier] = sourceFilter + notifiers[notifier] = sourceFilter.toMutableSet() Log.d(LOG_ID, "notifier size: " + notifiers.size.toString() ) if(timeChanged) checkTimeNotifierChanged(context) @@ -92,6 +92,7 @@ object InternalNotifier { private fun hasSource(notifier: NotifierInterface, notifySource: NotifySource): Boolean { if(hasNotifier(notifier)) { + Log.v(LOG_ID, "Check notifier ${notifier} has source $notifySource: ${notifiers[notifier]}") return notifiers[notifier]!!.contains(notifySource) } return false diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt index f850b8e00..1790ec87a 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt @@ -25,5 +25,6 @@ enum class NotifySource { NOTIFICATION_STOPPED, COMMAND, NEW_VERSION_AVAILABLE, - TTS_STATE_CHANGED; + TTS_STATE_CHANGED, + DISPLAY_STATE_CHANGED; } \ No newline at end of file diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/preferences/PreferenceHelper.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/preferences/PreferenceHelper.kt new file mode 100644 index 000000000..cb82eeb73 --- /dev/null +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/preferences/PreferenceHelper.kt @@ -0,0 +1,41 @@ +package de.michelinside.glucodatahandler.common.preferences + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.util.Log +import androidx.preference.Preference +import de.michelinside.glucodatahandler.common.Constants + +object PreferenceHelper { + private val LOG_ID = "GDH.PreferenceHelper" + + private fun checkReplaceSecondString(text: String): String { + if(Constants.IS_SECOND) + return text.replace("GlucoDataHandler", "GDH Second").replace("GlucoDataAuto", "GDA Second") + return text + } + + fun replaceSecondSummary(pref: Preference?) { + pref?.summary = checkReplaceSecondString(pref?.summary.toString()) + } + + fun replaceSecondTitle(pref: Preference?) { + pref?.title = checkReplaceSecondString(pref?.title.toString()) + } + + fun setLinkOnClick(preference: Preference?, linkResId: Int, context: Context) { + preference?.setOnPreferenceClickListener { + try { + val browserIntent = Intent( + Intent.ACTION_VIEW, + Uri.parse(context.resources.getText(linkResId).toString()) + ) + context.startActivity(browserIntent) + } catch (exc: Exception) { + Log.e(LOG_ID, "setLinkOnClick exception for key ${preference.key}" + exc.toString()) + } + true + } + } +} \ No newline at end of file diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceOnlineFragment.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/preferences/SourceOnlineFragment.kt similarity index 90% rename from mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceOnlineFragment.kt rename to common/src/main/java/de/michelinside/glucodatahandler/common/preferences/SourceOnlineFragment.kt index fa8e2340b..8a0ea8eda 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceOnlineFragment.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/preferences/SourceOnlineFragment.kt @@ -1,4 +1,4 @@ -package de.michelinside.glucodatahandler.preferences +package de.michelinside.glucodatahandler.common.preferences import android.content.Context import android.content.Intent @@ -8,7 +8,6 @@ import android.os.Bundle import android.text.InputType import android.util.Log import androidx.preference.* -import de.michelinside.glucodatahandler.R import de.michelinside.glucodatahandler.common.R as CR import de.michelinside.glucodatahandler.common.Constants import de.michelinside.glucodatahandler.common.notifier.InternalNotifier @@ -27,12 +26,15 @@ class SourceOnlineFragment : PreferenceFragmentCompat(), SharedPreferences.OnSha try { settingsChanged = false preferenceManager.sharedPreferencesName = Constants.SHARED_PREF_TAG - setPreferencesFromResource(R.xml.sources_online, rootKey) + setPreferencesFromResource(CR.xml.sources_online, rootKey) setPasswordPref(Constants.SHARED_PREF_LIBRE_PASSWORD) setPasswordPref(Constants.SHARED_PREF_DEXCOM_SHARE_PASSWORD) setPasswordPref(Constants.SHARED_PREF_NIGHTSCOUT_SECRET) + + PreferenceHelper.setLinkOnClick(findPreference("source_librelinkup_video"), CR.string.video_tutorial_librelinkup, requireContext()) + setupLibrePatientData() } catch (exc: Exception) { Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString()) @@ -62,11 +64,11 @@ class SourceOnlineFragment : PreferenceFragmentCompat(), SharedPreferences.OnSha override fun onResume() { Log.d(LOG_ID, "onResume called") try { + super.onResume() preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) updateEnableStates(preferenceManager.sharedPreferences!!) InternalNotifier.addNotifier(requireContext(), this, mutableSetOf(NotifySource.PATIENT_DATA_CHANGED)) update() - super.onResume() } catch (exc: Exception) { Log.e(LOG_ID, "onResume exception: " + exc.toString()) } @@ -75,9 +77,9 @@ class SourceOnlineFragment : PreferenceFragmentCompat(), SharedPreferences.OnSha override fun onPause() { Log.d(LOG_ID, "onPause called") try { + super.onPause() preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) InternalNotifier.remNotifier(requireContext(), this) - super.onPause() } catch (exc: Exception) { Log.e(LOG_ID, "onPause exception: " + exc.toString()) } @@ -98,7 +100,8 @@ class SourceOnlineFragment : PreferenceFragmentCompat(), SharedPreferences.OnSha updateEnableStates(sharedPreferences!!) update() } - Constants.SHARED_PREF_LIBRE_PATIENT_ID -> { + Constants.SHARED_PREF_LIBRE_PATIENT_ID, + Constants.SHARED_PREF_LIBRE_SERVER -> { update() } Constants.SHARED_PREF_DEXCOM_SHARE_USE_US_URL -> { @@ -152,8 +155,21 @@ class SourceOnlineFragment : PreferenceFragmentCompat(), SharedPreferences.OnSha value } } + + private fun setListSummary(key: String, defaultResId: Int) { + val listPref = findPreference(key) + if(listPref != null) { + val value = listPref.entry + listPref.summary = if(value.isNullOrEmpty()) + resources.getString(defaultResId) + else + value + } + } + private fun update() { setSummary(Constants.SHARED_PREF_LIBRE_USER, CR.string.src_libre_user_summary) + setListSummary(Constants.SHARED_PREF_LIBRE_SERVER, CR.string.source_libre_server_default) setSummary(Constants.SHARED_PREF_NIGHTSCOUT_URL, CR.string.src_ns_url_summary) setSummary(Constants.SHARED_PREF_DEXCOM_SHARE_USER, CR.string.src_dexcom_share_user_summary) updateDexcomAccountLink() @@ -200,14 +216,7 @@ class SourceOnlineFragment : PreferenceFragmentCompat(), SharedPreferences.OnSha val prefLink = findPreference(Constants.SHARED_PREF_DEXCOM_SHARE_ACCOUNT_LINK) if (prefLink != null) { prefLink.summary = resources.getString(if(us_account) CR.string.dexcom_share_check_us_account else CR.string.dexcom_share_check_non_us_account) - prefLink.setOnPreferenceClickListener { - val browserIntent = Intent( - Intent.ACTION_VIEW, - Uri.parse(resources.getString(if(us_account)CR.string.dexcom_account_us_url else CR.string.dexcom_account_non_us_url)) - ) - startActivity(browserIntent) - true - } + PreferenceHelper.setLinkOnClick(prefLink, if(us_account)CR.string.dexcom_account_us_url else CR.string.dexcom_account_non_us_url, requireContext()) } } } catch (exc: Exception) { diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/receiver/BatteryReceiver.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/receiver/BatteryReceiver.kt index 2bc6030d5..2bec02368 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/receiver/BatteryReceiver.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/receiver/BatteryReceiver.kt @@ -5,6 +5,8 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.util.Log +import de.michelinside.glucodatahandler.common.Constants +import de.michelinside.glucodatahandler.common.GlucoDataService import de.michelinside.glucodatahandler.common.notifier.* class BatteryReceiver: BroadcastReceiver() { @@ -14,11 +16,20 @@ class BatteryReceiver: BroadcastReceiver() { if (intent.extras == null || intent.extras!!.isEmpty) { return } + val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) + val enabled = sharedPref!!.getBoolean(Constants.SHARED_PREF_BATTERY_RECEIVER_ENABLED, true) val curValue = intent.extras!!.getInt(LEVEL, -1) - Log.i(LOG_ID, "Received batter level: " + curValue.toString() + "%") - if (curValue >= 0 && curValue != batteryPercentage) { - batteryPercentage = curValue - InternalNotifier.notify(context, NotifySource.BATTERY_LEVEL, batteryBundle) + Log.i(LOG_ID, "Received batter level: " + curValue.toString() + "% - enabled: $enabled") + if (enabled) { + if (curValue >= 0 && curValue != batteryPercentage) { + batteryPercentage = curValue + InternalNotifier.notify(context, NotifySource.BATTERY_LEVEL, batteryBundle) + } + } else { + // used as watchdog only! + GlucoDataService.checkServices(context) + if(batteryPercentage != 0) + batteryPercentage = 0 } } catch (exc: Exception) { Log.e(LOG_ID, "BatteryReceiver exception: " + exc.message.toString() ) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/receiver/ScreenEventReceiver.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/receiver/ScreenEventReceiver.kt new file mode 100644 index 000000000..964b6cd31 --- /dev/null +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/receiver/ScreenEventReceiver.kt @@ -0,0 +1,75 @@ +package de.michelinside.glucodatahandler.common.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.hardware.display.DisplayManager +import android.util.Log +import android.view.Display +import de.michelinside.glucodatahandler.common.ReceiveData +import de.michelinside.glucodatahandler.common.notifier.InternalNotifier +import de.michelinside.glucodatahandler.common.notifier.NotifySource + + +class ScreenEventReceiver: BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + try { + Log.i(LOG_ID, "onReceive called for action ${intent.action}") + update(context) + } catch (exc: Exception) { + Log.e(LOG_ID, "onReceive exception: " + exc.message.toString() ) + } + } + + companion object { + private val LOG_ID = "GDH.ScreenEventReceiver" + private var displayState = Display.STATE_ON + private var displayManager: DisplayManager? = null + fun isDisplayOff() : Boolean { + return displayState == Display.STATE_OFF + } + fun isDisplayOn() : Boolean { + return displayState == Display.STATE_ON + } + fun onDisplayOn(context: Context) { + Log.i(LOG_ID, "onDisplayOn called") + ReceiveData.forceObsoleteOnScreenOff = false + InternalNotifier.notify(context, NotifySource.DISPLAY_STATE_CHANGED, null) + } + fun onDisplayOff(context: Context) { + Log.i(LOG_ID, "onDisplayOff called") + InternalNotifier.notify(context, NotifySource.DISPLAY_STATE_CHANGED, null) + } + + fun reset(context: Context) { + Log.d(LOG_ID, "reset called") + if(displayManager != null) { + if(displayState == Display.STATE_OFF) { + displayState = Display.STATE_ON + onDisplayOn(context) + } + } + } + + fun update(context: Context) { + Log.v(LOG_ID, "update called") + if(displayManager == null) + displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager? + if(displayManager != null && displayManager!!.displays.isNotEmpty()) { + val display = displayManager!!.displays[0] + if(displayState != display.state) { + Log.i(LOG_ID, "Change display state from $displayState to ${display.state}") + val oldState = displayState + displayState = display.state + // state doze (2) is also handled as on! + if(displayState == Display.STATE_OFF) { + onDisplayOff(context) + } else if(oldState == Display.STATE_OFF) { + onDisplayOn(context) + } + } + } + } + + } +} \ No newline at end of file diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/DataSourceTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/DataSourceTask.kt index 2b98b5009..520de9439 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/DataSourceTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/DataSourceTask.kt @@ -5,9 +5,12 @@ import android.content.Intent import android.content.SharedPreferences import android.os.Bundle import android.util.Log +import de.michelinside.glucodatahandler.common.AppSource import de.michelinside.glucodatahandler.common.Constants import de.michelinside.glucodatahandler.common.GlucoDataService import de.michelinside.glucodatahandler.common.Intents +import de.michelinside.glucodatahandler.common.R +import de.michelinside.glucodatahandler.common.ReceiveData import de.michelinside.glucodatahandler.common.SourceState import de.michelinside.glucodatahandler.common.SourceStateData import de.michelinside.glucodatahandler.common.notifier.DataSource @@ -38,6 +41,9 @@ abstract class DataSourceTask(private val enabledKey: String, protected val sour Constants.SHARED_PREF_LIBRE_USER, Constants.SHARED_PREF_LIBRE_PASSWORD, Constants.SHARED_PREF_LIBRE_RECONNECT, + Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU, + Constants.SHARED_PREF_LIBRE_PATIENT_ID, + Constants.SHARED_PREF_LIBRE_SERVER, Constants.SHARED_PREF_NIGHTSCOUT_URL, Constants.SHARED_PREF_NIGHTSCOUT_SECRET, Constants.SHARED_PREF_NIGHTSCOUT_TOKEN, @@ -56,6 +62,9 @@ abstract class DataSourceTask(private val enabledKey: String, protected val sour putString(Constants.SHARED_PREF_LIBRE_USER, bundle.getString(Constants.SHARED_PREF_LIBRE_USER, "")) putString(Constants.SHARED_PREF_LIBRE_PASSWORD, bundle.getString(Constants.SHARED_PREF_LIBRE_PASSWORD, "")) putBoolean(Constants.SHARED_PREF_LIBRE_RECONNECT, bundle.getBoolean(Constants.SHARED_PREF_LIBRE_RECONNECT, false)) + putBoolean(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU, bundle.getBoolean(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU, true)) + putString(Constants.SHARED_PREF_LIBRE_SERVER, bundle.getString(Constants.SHARED_PREF_LIBRE_SERVER, "io")) + putString(Constants.SHARED_PREF_LIBRE_PATIENT_ID, bundle.getString(Constants.SHARED_PREF_LIBRE_PATIENT_ID, "")) putString(Constants.SHARED_PREF_NIGHTSCOUT_URL, bundle.getString(Constants.SHARED_PREF_NIGHTSCOUT_URL, "")) putString(Constants.SHARED_PREF_NIGHTSCOUT_SECRET, bundle.getString(Constants.SHARED_PREF_NIGHTSCOUT_SECRET, "")) @@ -78,6 +87,9 @@ abstract class DataSourceTask(private val enabledKey: String, protected val sour bundle.putString(Constants.SHARED_PREF_LIBRE_USER, sharedPref.getString(Constants.SHARED_PREF_LIBRE_USER, "")) bundle.putString(Constants.SHARED_PREF_LIBRE_PASSWORD, sharedPref.getString(Constants.SHARED_PREF_LIBRE_PASSWORD, "")) bundle.putBoolean(Constants.SHARED_PREF_LIBRE_RECONNECT, sharedPref.getBoolean(Constants.SHARED_PREF_LIBRE_RECONNECT, false)) + bundle.putBoolean(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU, sharedPref.getBoolean(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU, true)) + bundle.putString(Constants.SHARED_PREF_LIBRE_SERVER, sharedPref.getString(Constants.SHARED_PREF_LIBRE_SERVER, "io")) + bundle.putString(Constants.SHARED_PREF_LIBRE_PATIENT_ID, sharedPref.getString(Constants.SHARED_PREF_LIBRE_PATIENT_ID, "")) bundle.putString(Constants.SHARED_PREF_NIGHTSCOUT_URL, sharedPref.getString(Constants.SHARED_PREF_NIGHTSCOUT_URL, "")) bundle.putString(Constants.SHARED_PREF_NIGHTSCOUT_SECRET, sharedPref.getString(Constants.SHARED_PREF_NIGHTSCOUT_SECRET, "")) @@ -95,26 +107,26 @@ abstract class DataSourceTask(private val enabledKey: String, protected val sour var lastState = SourceState.NONE var lastErrorCode: Int = -1 - fun setLastError(error: String, code: Int = -1) { - setState( SourceState.ERROR, error, code) + fun setLastError(error: String, code: Int = -1, message: String = "") { + setState(SourceState.ERROR, error, code, message) } - fun setState(state: SourceState, error: String = "", code: Int = -1) { + fun setState(state: SourceState, error: String = "", code: Int = -1, message: String = "") { when (state) { SourceState.NONE -> { - Log.v(LOG_ID,"Set state for source " + source + ": " + state + " - " + error + " (" + code + ")") + Log.v(LOG_ID, "Set state for source $source: $state - $error ($code) - message: $message") } SourceState.CONNECTED -> { - Log.i(LOG_ID,"Set connected for source " + source) + Log.i(LOG_ID, "Set connected for source $source") } else -> { - Log.w(LOG_ID,"Set state for source " + source + ": " + state + " - " + error + " (" + code + ")") + Log.w(LOG_ID, "Set state for source $source: $state - $error ($code) - message: $message") } } lastErrorCode = code lastState = state - SourceStateData.setState(source, state, getErrorMessage(state, error, code)) + SourceStateData.setState(source, state, getErrorMessage(state, error, code), message) } private fun getErrorMessage(state: SourceState, error: String, code: Int): String { @@ -126,7 +138,7 @@ abstract class DataSourceTask(private val enabledKey: String, protected val sour result += error return result } - return "" + return error } private fun isShortInterval(): Boolean { @@ -138,6 +150,13 @@ abstract class DataSourceTask(private val enabledKey: String, protected val sour } } + private fun isLongInterval(): Boolean { + return when(lastState) { + SourceState.ERROR -> lastErrorCode == 429 + else -> false + } + } + open fun authenticate(): Boolean = true open fun reset() {} @@ -192,8 +211,24 @@ abstract class DataSourceTask(private val enabledKey: String, protected val sour } } + open fun getNoNewValueInfo(time: Long): String = "" + protected fun handleResult(extras: Bundle) { Log.d(LOG_ID, "handleResult for $source: ${Utils.dumpBundle(extras)}") + if(!ReceiveData.hasNewValue(extras)) { + if(extras.containsKey(ReceiveData.TIME)) { + setState( + SourceState.NO_NEW_VALUE, + GlucoDataService.context!!.resources.getString(R.string.last_value_on_server, Utils.getUiTimeStamp(extras.getLong(ReceiveData.TIME))), + -1, + getNoNewValueInfo(extras.getLong(ReceiveData.TIME)) + ) + } else { + setState(SourceState.NO_NEW_VALUE) + } + return + } + val intent = Intent(GlucoDataService.context!!, InternalActionReceiver::class.java) intent.action = Intents.GLUCODATA_ACTION intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) @@ -240,7 +275,22 @@ abstract class DataSourceTask(private val enabledKey: String, protected val sour Log.d(LOG_ID, "handleResult for " + source + " done!") } + private fun getErrorMessage(code: Int, message: String?): String { + when(code) { + 429 -> return GlucoDataService.context!!.resources.getString(R.string.http_error_429) + else -> return message ?: "Error" + } + } + + private fun getErrorInfo(code: Int): String { + when(code) { + 429 -> return GlucoDataService.context!!.resources.getString(R.string.http_error_429_info) + else -> return "" + } + } + open fun checkErrorResponse(code: Int, message: String?, errorResponse: String? = null) { + Log.d(LOG_ID, "checkErrorResponse: $code - $message - $errorResponse") if (code in 400..499) { reset() // reset token for client error -> trigger reconnect if(firstGetValue) { @@ -248,7 +298,11 @@ abstract class DataSourceTask(private val enabledKey: String, protected val sour return } } - setLastError(message ?: code.toString(), code) + var errorMessage = getErrorMessage(code, message) + if(errorResponse != null) { + errorMessage += "\n" + errorResponse + } + setLastError(errorMessage, code, getErrorInfo(code)) } open fun getTrustAllCertificates(): Boolean = false @@ -264,13 +318,7 @@ abstract class DataSourceTask(private val enabledKey: String, protected val sour private fun checkResponse(responseCode: Int): String? { if (responseCode != HttpURLConnection.HTTP_OK) { - if(responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR) { - if (httpRequest.responseError != null ) { - checkErrorResponse(responseCode, httpRequest.responseMessage, httpRequest.responseError) - return null - } - } - checkErrorResponse(responseCode, httpRequest.responseMessage) + checkErrorResponse(responseCode, httpRequest.responseMessage, httpRequest.responseError) return null } return httpRequest.response @@ -280,15 +328,18 @@ abstract class DataSourceTask(private val enabledKey: String, protected val sour return checkResponse(httpRequest.get(url, header, getTrustAllCertificates())) } - protected fun httpPost(url: String, header: MutableMap, postData: String): String? { + protected fun httpPost(url: String, header: MutableMap, postData: String?): String? { return checkResponse(httpRequest.post(url, postData, header, getTrustAllCertificates())) } override fun getIntervalMinute(): Long { - if (interval > 1 && isShortInterval()) { + if (interval > 1L && isShortInterval()) { Log.d(LOG_ID, "Use short interval of 1 minute.") return 1 // retry after a minute } + if (interval == 1L && isLongInterval()) { + return 5 // increase interval for case of "429: Too many requests" + } return interval } @@ -317,6 +368,8 @@ abstract class DataSourceTask(private val enabledKey: String, protected val sour setEnabled(sharedPreferences.getBoolean(enabledKey, false)) interval = sharedPreferences.getString(Constants.SHARED_PREF_SOURCE_INTERVAL, "1")?.toLong() ?: 1L delaySec = sharedPreferences.getInt(Constants.SHARED_PREF_SOURCE_DELAY, 10).toLong() + if(GlucoDataService.appSource == AppSource.WEAR_APP) + delaySec += 5L // add 5 seconds delay to receive by phone if connected return true } else { var result = false @@ -333,6 +386,8 @@ abstract class DataSourceTask(private val enabledKey: String, protected val sour Constants.SHARED_PREF_SOURCE_DELAY -> { if (delaySec != sharedPreferences.getInt(Constants.SHARED_PREF_SOURCE_DELAY, 10).toLong()) { delaySec = sharedPreferences.getInt(Constants.SHARED_PREF_SOURCE_DELAY, 10).toLong() + if(GlucoDataService.appSource == AppSource.WEAR_APP) + delaySec += 5L // add 5 seconds delay to receive by phone if connected result = true // retrigger alarm after delay has changed } } diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/DexcomShareSourceTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/DexcomShareSourceTask.kt index 1541beaf3..af25bfb62 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/DexcomShareSourceTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/DexcomShareSourceTask.kt @@ -7,6 +7,7 @@ import android.util.Log import de.michelinside.glucodatahandler.common.BuildConfig import de.michelinside.glucodatahandler.common.Constants import de.michelinside.glucodatahandler.common.GlucoDataService +import de.michelinside.glucodatahandler.common.R import de.michelinside.glucodatahandler.common.ReceiveData import de.michelinside.glucodatahandler.common.SourceState import de.michelinside.glucodatahandler.common.notifier.DataSource @@ -118,7 +119,7 @@ class DexcomShareSourceTask : DataSourceTask(Constants.SHARED_PREF_DEXCOM_SHARE_ } private fun handleValueResponse(body: String?): Boolean { - Log.d(LOG_ID, "handleValueResponse called: $body") + Log.i(LOG_ID, "handleValueResponse called: $body") if (body == null) { return false } @@ -174,7 +175,7 @@ class DexcomShareSourceTask : DataSourceTask(Constants.SHARED_PREF_DEXCOM_SHARE_ val dataArray = JSONArray(body) if(dataArray.length() == 0) { - setState(SourceState.NO_NEW_VALUE) + setState(SourceState.NO_NEW_VALUE, GlucoDataService.context!!.resources.getString(R.string.no_data_in_server_response)) return false } val data = dataArray.getJSONObject(dataArray.length()-1) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt index 9e1be11b4..79f322c9b 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt @@ -22,7 +22,7 @@ class ElapsedTimeTask : BackgroundTask() { val isActive: Boolean get() { Log.v(LOG_ID, "Check active: - has notifier: ${InternalNotifier.hasTimeNotifier} - relativeTime: $relativeTime - interval: $interval") - return (relativeTime || interval > 0 || InternalNotifier.hasTimeNotifier) + return (InternalNotifier.hasTimeNotifier && (relativeTime || interval > 0)) } } diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreLinkSourceTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreLinkSourceTask.kt index 7c0c9b299..b80894ca3 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreLinkSourceTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreLinkSourceTask.kt @@ -35,17 +35,23 @@ class LibreLinkSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, private var tokenExpire = 0L private var userId = "" private var region = "" + private var topLevelDomain = "io" private var patientId = "" private var dataReceived = false // mark this endpoint as already received data + private var autoAcceptTOU = true val patientData = mutableMapOf() - const val server = "https://api.libreview.io" - const val region_server = "https://api-%s.libreview.io" + const val server = "https://api.libreview.%s" + const val region_server = "https://api-%s.libreview.%s" const val LOGIN_ENDPOINT = "/llu/auth/login" const val CONNECTION_ENDPOINT = "/llu/connections" + const val GRAPH_ENDPOINT = "/llu/connections/%s/graph" + const val ACCEPT_ENDPOINT = "/auth/continue/" + const val ACCEPT_TERMS_TYPE = "tou" + const val ACCEPT_TOKEN_TYPE = "pp" } private fun getUrl(endpoint: String): String { - val url = (if(region.isEmpty()) server else region_server.format(region)) + endpoint + val url = (if(region.isEmpty()) server.format(topLevelDomain) else region_server.format(region, topLevelDomain)) + endpoint Log.i(LOG_ID, "Using URL: " + url) return url } @@ -53,28 +59,28 @@ class LibreLinkSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, private fun getHeader(): MutableMap { val result = mutableMapOf( "product" to "llu.android", - "version" to "4.12.0", + "version" to "4.13.0", "Accept" to "application/json", "Content-Type" to "application/json", - "cache-control" to "no-cache" + "cache-control" to "no-cache", + "Account-Id" to Utils.encryptSHA256(userId) ) if (token.isNotEmpty()) { result["Authorization"] = "Bearer " + token - if(userId.isNotEmpty()) { - result["Account-Id"] = Utils.encryptSHA256(userId) - } } + Log.v(LOG_ID, "Header: ${result}") return result } override fun reset() { Log.i(LOG_ID, "reset called") token = "" - userId = "" tokenExpire = 0L region = "" + userId = "" dataReceived = false patientData.clear() + patientId = GlucoDataService.sharedPref!!.getString(Constants.SHARED_PREF_LIBRE_PATIENT_ID, "")!! try { Log.d(LOG_ID, "Save reset") with (GlucoDataService.sharedPref!!.edit()) { @@ -89,32 +95,138 @@ class LibreLinkSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, } } - val sensitivData = mutableSetOf("id", "patientId", "firstName", "lastName", "did", "sn", "token", "deviceId", "email", "primaryValue", "secondaryValue" ) + val sensitivData = mutableSetOf("id", "patientId", "firstName", "lastName", "did", "token", "deviceId", "email", "primaryValue", "secondaryValue" ) private fun replaceSensitiveData(body: String): String { - var result = body - sensitivData.forEach { - val groups = Regex("\"$it\":\"(.*?)\"").find(result)?.groupValues - if(!groups.isNullOrEmpty() && groups.size > 1 && groups[1].isNotEmpty()) { - val replaceValue = groups[0].replace(groups[1], "---") - result = result.replace(groups[0], replaceValue) + try { + var result = body + sensitivData.forEach { + val groups = Regex("\"$it\":\"(.*?)\"").find(result)?.groupValues + if(!groups.isNullOrEmpty() && groups.size > 1 && groups[1].isNotEmpty()) { + val replaceValue = groups[0].replace(groups[1], "---") + result = result.replace(groups[0], replaceValue) + } } + return result.take(1000) + } catch (exc: Exception) { + Log.e(LOG_ID, "replaceSensitiveData exception: " + exc.toString() ) + return body } - return result } - private fun checkResponse(body: String?): JSONObject? { + /* + After tou there will be pp for re-new token! + Example for error 4 for tou: + { + "status": 4, + "data": { + "step": { + "type": "tou", + "componentName": "AcceptDocument", + "props": { + "reaccept": true, + "titleKey": "Common.termsOfUse", + "type": "tou" + } + }, + "user": { + "accountType": "pat", + "country": "CH", + "uiLanguage": "en-US" + }, + "authTicket": { + "token": "the token here", + "expires": 1719337966, + "duration": 3600000 + } + } + } + */ + + private fun getStatusMessage(status: Int): String { + return when(status) { + 2 -> GlucoDataService.context!!.getString(R.string.src_librelink_error2) + 4 -> GlucoDataService.context!!.getString(R.string.src_librelink_error4) + else -> "" + } + } + + override fun getNoNewValueInfo(time: Long): String { + if(Utils.getElapsedTimeMinute(time) > 5) + return GlucoDataService.context!!.resources.getString(R.string.libre_no_new_value_info) + return "" + } + + private fun getError4Message(type: String? = null): String { + if(type.isNullOrEmpty()) { + return getStatusMessage(4) + } + if(type == ACCEPT_TERMS_TYPE && !autoAcceptTOU) { + return GlucoDataService.context!!.getString(R.string.src_librelink_error4_tou) + } + return GlucoDataService.context!!.getString(R.string.src_librelink_error4_type, type) + } + + private fun handleError4(jsonObj: JSONObject, lastType: String = ""): JSONObject? { + // handle error 4 which contains the steps to do, like accept user terms or accept token + reset() + if (jsonObj.has("data")) { + val data = jsonObj.getJSONObject("data") + checkRedirect(data) + if(data.has("step")) { + val step = data.getJSONObject("step") + Log.i(LOG_ID, "Handle error 4 step: $step") + if(step.has("type")) { + val type = step.getString("type") + Log.i(LOG_ID, "Handle error 4 with type: $type - lastType: $lastType") + getToken(data) + if(token.isEmpty()) { + setLastError(step.optString("componentName"), 4, getError4Message(type)) + return null + } + if(lastType != type && type == ACCEPT_TOKEN_TYPE || (type == ACCEPT_TERMS_TYPE && autoAcceptTOU )) { + // send accept request and re-login + Log.i(LOG_ID, "Send accept request for type $type") + return checkResponse(httpPost(getUrl(ACCEPT_ENDPOINT + type), getHeader(), null), type) + } else { + setLastError(step.optString("componentName"), 4, getError4Message(type)) + } + return null + } + } + } + // else + setLastError(getErrorMessage(jsonObj), 4, getStatusMessage(4)) + return null + } + + private fun getErrorMessage(jsonObj: JSONObject): String { + if(jsonObj.has("error")) { + val errorObj = jsonObj.optJSONObject("error") + if(errorObj?.has("message") == true) + return errorObj.optString("message", "Error")?: "Error" + } + if(jsonObj.has("data")) { + val dataObj = jsonObj.optJSONObject("data") + if(dataObj?.has("message") == true) + return dataObj.optString("message", "Error")?: "Error" + } + return "Error" + } + + private fun checkResponse(body: String?, lastError4Type: String = ""): JSONObject? { if (body.isNullOrEmpty()) { return null } - Log.d(LOG_ID, "Handle json response: " + replaceSensitiveData(body)) + Log.i(LOG_ID, "Handle json response: " + replaceSensitiveData(body)) val jsonObj = JSONObject(body) if (jsonObj.has("status")) { val status = jsonObj.optInt("status", -1) - if(status != 0) { + if (status == 4) { + return handleError4(jsonObj, lastError4Type) + } else if(status != 0) { if(jsonObj.has("error")) { - val error = jsonObj.optJSONObject("error")?.optString("message", "") - setLastError(error?: "Error", status) + setLastError(getErrorMessage(jsonObj), status, getStatusMessage(status)) return null } setLastError("Error", status) @@ -164,50 +276,62 @@ class LibreLinkSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, if (jsonObject != null) { val data = jsonObject.optJSONObject("data") if (data != null) { - if (data.has("redirect") && data.optBoolean("redirect")) { - if (data.has("region")) { - region = data.optString("region", "") - Log.i(LOG_ID, "Handle redirect to region: " + region) - saveRegion() - return authenticate() - } else { - setLastError("redirect without region!!!", 500) - } + if(checkRedirect(data)) + return authenticate() + getToken(data) + } + } + return token.isNotEmpty() + } + + private fun checkRedirect(data: JSONObject): Boolean { + Log.d(LOG_ID, "Check for redirect") + if (data.has("redirect") && data.optBoolean("redirect")) { + if (data.has("region")) { + region = data.optString("region", "") + Log.i(LOG_ID, "Handle redirect to region: " + region) + saveRegion() + return true + } else { + setLastError("redirect without region!!!", 500) + } + } + return false + } + + private fun getToken(data: JSONObject) { + Log.d(LOG_ID, "Check for new token token") + if(data.has("user")) { + val user = data.optJSONObject("user") + if(user != null && user.has("id")) { + userId = user.optString("id") + Log.i(LOG_ID, "User ID set!") + with(GlucoDataService.sharedPref!!.edit()) { + putString(Constants.SHARED_PREF_LIBRE_USER_ID, userId) + apply() } - if(data.has("user")) { - val user = data.optJSONObject("user") - if(user != null && user.has("id")) { - userId = user.optString("id") - Log.i(LOG_ID, "User ID set!") - with(GlucoDataService.sharedPref!!.edit()) { - putString(Constants.SHARED_PREF_LIBRE_USER_ID, userId) - apply() - } + } + } + if (data.has("authTicket")) { + val authTicket = data.optJSONObject("authTicket") + if (authTicket != null) { + if (authTicket.has("token")) { + token = authTicket.optString("token", "") + tokenExpire = authTicket.optLong("expires", 0L) * 1000 + if (token.isNotEmpty()) { + Log.i(LOG_ID, "Login succeeded! Token expires at " + DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT).format( + Date(tokenExpire) + )) } - } - if (data.has("authTicket")) { - val authTicket = data.optJSONObject("authTicket") - if (authTicket != null) { - if (authTicket.has("token")) { - token = authTicket.optString("token", "") - tokenExpire = authTicket.optLong("expires", 0L) * 1000 - if (token.isNotEmpty()) { - Log.i(LOG_ID, "Login succeeded! Token expires at " + DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT).format( - Date(tokenExpire) - )) - } - with(GlucoDataService.sharedPref!!.edit()) { - putString(Constants.SHARED_PREF_LIBRE_TOKEN, token) - putLong(Constants.SHARED_PREF_LIBRE_TOKEN_EXPIRE, tokenExpire) - putBoolean(Constants.SHARED_PREF_LIBRE_RECONNECT, false) - apply() - } - } + with(GlucoDataService.sharedPref!!.edit()) { + putString(Constants.SHARED_PREF_LIBRE_TOKEN, token) + putLong(Constants.SHARED_PREF_LIBRE_TOKEN_EXPIRE, tokenExpire) + putBoolean(Constants.SHARED_PREF_LIBRE_RECONNECT, false) + apply() } } } } - return token.isNotEmpty() } private fun saveRegion() { @@ -245,6 +369,9 @@ class LibreLinkSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, } override fun getValue(): Boolean { + if(patientId.isNotEmpty() && patientData.isNotEmpty()) { + return handleGraphResponse(httpGet(getUrl(GRAPH_ENDPOINT.format(patientId)), getHeader())) + } return handleGlucoseResponse(httpGet(getUrl(CONNECTION_ENDPOINT), getHeader())) } @@ -273,6 +400,23 @@ class LibreLinkSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, return format.parse(time)!!.time } + private fun handleGraphResponse(body: String?): Boolean { + val jsonObject = checkResponse(body) + if (jsonObject != null && jsonObject.has("data")) { + val data = jsonObject.optJSONObject("data") + if(data != null && data.has("connection")) { + val connection = data.optJSONObject("connection") + if(connection!=null) + return parseConnectionData(connection) + } + } + Log.e(LOG_ID, "No data found in response: ${replaceSensitiveData(body!!)}") + setLastError("Invalid response! Please send logs to developer.") + reset() + retry = true + return false + } + private fun handleGlucoseResponse(body: String?): Boolean { /* { @@ -317,41 +461,10 @@ class LibreLinkSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, } val data = getPatientData(array) if (data == null) { - setState(SourceState.NO_NEW_VALUE) + setState(SourceState.NO_NEW_VALUE, GlucoDataService.context!!.resources.getString(R.string.no_data_in_server_response)) return false } - if(data.has("glucoseMeasurement")) { - val glucoseData = data.optJSONObject("glucoseMeasurement") - if (glucoseData != null) { - val glucoExtras = Bundle() - val parsedUtc = parseUtcTimestamp(glucoseData.optString("FactoryTimestamp")) - val parsedLocal = parseLocalTimestamp(glucoseData.optString("Timestamp")) - Log.d(LOG_ID, "UTC->local: " + parsedUtc + " - local: " + parsedLocal) - if (parsedUtc > 0) - glucoExtras.putLong(ReceiveData.TIME, parsedUtc) - else - glucoExtras.putLong(ReceiveData.TIME, parsedLocal) - glucoExtras.putFloat(ReceiveData.GLUCOSECUSTOM, glucoseData.optDouble("Value").toFloat()) - glucoExtras.putInt(ReceiveData.MGDL, glucoseData.optInt("ValueInMgPerDl")) - glucoExtras.putFloat(ReceiveData.RATE, getRateFromTrend(glucoseData.optInt("TrendArrow"))) - if (glucoseData.optBoolean("isHigh")) - glucoExtras.putInt(ReceiveData.ALARM, 6) - else if (glucoseData.optBoolean("isLow")) - glucoExtras.putInt(ReceiveData.ALARM, 7) - else - glucoExtras.putInt(ReceiveData.ALARM, 0) - - glucoExtras.putString(ReceiveData.SERIAL, "LibreLink") - if (data.has("sensor")) { - val sensor = data.optJSONObject("sensor") - if (sensor != null && sensor.has("sn")) - glucoExtras.putString(ReceiveData.SERIAL, sensor.optString("sn")) - } - dataReceived = true - handleResult(glucoExtras) - return true - } - } + return parseConnectionData(data) } else { Log.e(LOG_ID, "No data array found in response: ${replaceSensitiveData(body!!)}") setLastError("Invalid response! Please send logs to developer.") @@ -362,10 +475,48 @@ class LibreLinkSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, return false } + private fun parseConnectionData(data: JSONObject): Boolean { + if(data.has("glucoseMeasurement")) { + val glucoseData = data.optJSONObject("glucoseMeasurement") + if (glucoseData != null) { + val glucoExtras = Bundle() + val parsedUtc = parseUtcTimestamp(glucoseData.optString("FactoryTimestamp")) + val parsedLocal = parseLocalTimestamp(glucoseData.optString("Timestamp")) + Log.d(LOG_ID, "UTC->local: " + parsedUtc + " - local: " + parsedLocal) + if (parsedUtc > 0) + glucoExtras.putLong(ReceiveData.TIME, parsedUtc) + else + glucoExtras.putLong(ReceiveData.TIME, parsedLocal) + glucoExtras.putFloat(ReceiveData.GLUCOSECUSTOM, glucoseData.optDouble("Value").toFloat()) + glucoExtras.putInt(ReceiveData.MGDL, glucoseData.optInt("ValueInMgPerDl")) + glucoExtras.putFloat(ReceiveData.RATE, getRateFromTrend(glucoseData.optInt("TrendArrow"))) + if (glucoseData.optBoolean("isHigh")) + glucoExtras.putInt(ReceiveData.ALARM, 6) + else if (glucoseData.optBoolean("isLow")) + glucoExtras.putInt(ReceiveData.ALARM, 7) + else + glucoExtras.putInt(ReceiveData.ALARM, 0) + + glucoExtras.putString(ReceiveData.SERIAL, "LibreLink") + if (data.has("sensor")) { + val sensor = data.optJSONObject("sensor") + if (sensor != null && sensor.has("sn")) + glucoExtras.putString(ReceiveData.SERIAL, sensor.optString("sn")) + } + dataReceived = true + handleResult(glucoExtras) + return true + } + } else { + Log.e(LOG_ID, "No glucoseMeasurement found in response: ${replaceSensitiveData(data.toString())}") + } + return false + } + private fun getPatientData(dataArray: JSONArray): JSONObject? { if(dataArray.length() > patientData.size) { // create patientData map - val checkPatienId = patientData.isEmpty() && patientId.isEmpty() + val checkPatienId = patientData.isEmpty() && patientId.isNotEmpty() patientData.clear() for (i in 0 until dataArray.length()) { val data = dataArray.getJSONObject(i) @@ -377,6 +528,7 @@ class LibreLinkSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, } } if (checkPatienId && !patientData.keys.contains(patientId)) { + Log.i(LOG_ID, "Reset patient ID as it is not in the list") patientId = "" with (GlucoDataService.sharedPref!!.edit()) { putString(Constants.SHARED_PREF_LIBRE_PATIENT_ID, "") @@ -388,6 +540,7 @@ class LibreLinkSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, } } if(patientId.isNotEmpty()) { + Log.d(LOG_ID, "Using patient ID $patientId") for (i in 0 until dataArray.length()) { val data = dataArray.getJSONObject(i) if (data.has("patientId") && data.getString("patientId") == patientId) { @@ -395,6 +548,9 @@ class LibreLinkSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, } } return null + } else if (patientData.isNotEmpty()) { + patientId = patientData.keys.first() + Log.d(LOG_ID, "Using patient ID $patientId") } // default: use first one return dataArray.optJSONObject(0) @@ -420,6 +576,8 @@ class LibreLinkSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, tokenExpire = sharedPreferences.getLong(Constants.SHARED_PREF_LIBRE_TOKEN_EXPIRE, 0L) region = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_REGION, "")!! patientId = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_PATIENT_ID, "")!! + autoAcceptTOU = sharedPreferences.getBoolean(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU, true) + topLevelDomain = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_SERVER, "io")?: "io" InternalNotifier.notify(GlucoDataService.context!!, NotifySource.SOURCE_STATE_CHANGE, null) trigger = true } else { @@ -454,6 +612,24 @@ class LibreLinkSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, trigger = true } } + Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU -> { + if (autoAcceptTOU != sharedPreferences.getBoolean(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU, true)) { + autoAcceptTOU = sharedPreferences.getBoolean(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU, true) + Log.i(LOG_ID, "Auto accept TOU changed to $autoAcceptTOU") + if(autoAcceptTOU && lastErrorCode == 4) + trigger = true + } + } + Constants.SHARED_PREF_LIBRE_SERVER -> { + if (topLevelDomain != sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_SERVER, "io")) { + topLevelDomain = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_SERVER, "io")?: "io" + if(topLevelDomain.isEmpty()) + topLevelDomain = "io" + Log.i(LOG_ID, "Top level domain changed to $topLevelDomain") + reset() + trigger = true + } + } } } return super.checkPreferenceChanged(sharedPreferences, key, context) || trigger diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/NightscoutSourceTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/NightscoutSourceTask.kt index b4aead0ff..121f31062 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/NightscoutSourceTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/NightscoutSourceTask.kt @@ -174,6 +174,7 @@ class NightscoutSourceTask: DataSourceTask(Constants.SHARED_PREF_NIGHTSCOUT_ENAB } else { glucoExtras.putFloat(ReceiveData.IOB, Float.NaN) glucoExtras.putFloat(ReceiveData.COB, Float.NaN) + glucoExtras.putLong(ReceiveData.IOBCOB_TIME, 0L) } handleResult(glucoExtras) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/ui/GlucoseEditPreference.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/ui/GlucoseEditPreference.kt index 654ade9aa..13480ec9b 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/ui/GlucoseEditPreference.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/ui/GlucoseEditPreference.kt @@ -50,31 +50,41 @@ class GlucoseEditPreference : EditTextPreference, OnBindEditTextListener { } private fun getPersistValue(value: String): Float { - var result = value.toFloat() - if (fromDialog) { - if ((isDelta && ReceiveData.isMmol) || (!isDelta && GlucoDataUtils.isMmolValue(value.toFloat()))) { - result = GlucoDataUtils.mmolToMg(result) + try { + var result = value.toFloat() + if (fromDialog) { + if ((isDelta && ReceiveData.isMmol) || (!isDelta && GlucoDataUtils.isMmolValue(value.toFloat()))) { + result = GlucoDataUtils.mmolToMg(result) + } } + Log.v(LOG_ID, "$value -> persistValue = $result - fromDialog: $fromDialog") + return result + } catch (exc: Exception) { + Log.e(LOG_ID, "getPersistValue exception: " + exc.toString()) } - Log.v(LOG_ID, "$value -> persistValue = $result - fromDialog: $fromDialog") - return result + Log.v(LOG_ID, "Returning default value $defaultValue") + return defaultValue } private fun getDisplayValue(curText: String): String { - val curDisplayValue: String - var value = curText.toFloat() - if (isNegative && value > 0) - value = -value - if (!ReceiveData.isMmol && !isDelta) { - if(GlucoDataUtils.isMmolValue(value)) - value = GlucoDataUtils.mmolToMg(value) - curDisplayValue = value.toInt().toString() - } else if (ReceiveData.isMmol && (!GlucoDataUtils.isMmolValue(value) || isDelta)) { - curDisplayValue = GlucoDataUtils.mgToMmol(value).toString() - } else { - curDisplayValue = value.toString() + var curDisplayValue = "" + try { + var value = curText.toFloat() + if (isNegative && value > 0) + value = -value + if (!ReceiveData.isMmol && !isDelta) { + if(GlucoDataUtils.isMmolValue(value)) + value = GlucoDataUtils.mmolToMg(value) + curDisplayValue = value.toInt().toString() + } else if (ReceiveData.isMmol && (!GlucoDataUtils.isMmolValue(value) || isDelta)) { + curDisplayValue = GlucoDataUtils.mgToMmol(value).toString() + } else { + curDisplayValue = value.toString() + } + Log.v(LOG_ID, "$curText -> display value = $curDisplayValue") + } catch (exc: Exception) { + Log.e(LOG_ID, "getDisplayValue exception: " + exc.toString()) } - Log.v(LOG_ID, "$curText -> display value = $curDisplayValue") return curDisplayValue } diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/utils/BitmapUtils.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/BitmapUtils.kt index b1ffda144..86a2acb62 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/utils/BitmapUtils.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/BitmapUtils.kt @@ -13,6 +13,7 @@ import android.graphics.drawable.Icon import android.util.Log import de.michelinside.glucodatahandler.common.Constants import de.michelinside.glucodatahandler.common.GlucoDataService +import de.michelinside.glucodatahandler.common.R import de.michelinside.glucodatahandler.common.ReceiveData import kotlin.math.abs @@ -36,7 +37,7 @@ object BitmapUtils { private fun isShortText(text: String): Boolean = text.length <= (if (text.contains(".")) 3 else 2) - fun calcMaxTextSizeForBitmap(bitmap: Bitmap, text: String, roundTarget: Boolean, maxTextSize: Float, top: Boolean, bold: Boolean): Float { + fun calcMaxTextSizeForBitmap(bitmap: Bitmap, text: String, roundTarget: Boolean, maxTextSize: Float, top: Boolean, bold: Boolean, useTallFont: Boolean = false): Float { var result: Float = maxTextSize if(roundTarget) { if (!top || !isShortText(text) ) { @@ -64,19 +65,25 @@ object BitmapUtils { val paint = Paint(Paint.ANTI_ALIAS_FLAG) paint.textSize = maxTextSize paint.textAlign = Paint.Align.CENTER - if (bold) + if (useTallFont) + paint.typeface = Typeface.create(GlucoDataService.context!!.resources.getFont(R.font.opensans), Typeface.BOLD) + else if (bold) paint.typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD) val boundsText = Rect() paint.getTextBounds(fullText, 0, fullText.length, boundsText) result = minOf( maxTextSize, (maxTextSize - 1) * bitmap.width / boundsText.width() ) + if(useTallFont && result < maxTextSize) { + result *= 0.95F // for values like 118 the 8 was cut... + } + Log.d(LOG_ID, "calculated max text size for $text ($fullText): $result") } return result } - fun textToBitmap(text: String, color: Int, roundTarget: Boolean = false, strikeThrough: Boolean = false, width: Int = 100, height: Int = 100, top: Boolean = false, bold: Boolean = false, resizeFactor: Float = 1F, withShadow: Boolean = false, bottom: Boolean = false): Bitmap? { + fun textToBitmap(text: String, color: Int, roundTarget: Boolean = false, strikeThrough: Boolean = false, width: Int = 100, height: Int = 100, top: Boolean = false, bold: Boolean = false, resizeFactor: Float = 1F, withShadow: Boolean = false, bottom: Boolean = false, useTallFont: Boolean = false): Bitmap? { try { val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888 ) - val maxTextSize = calcMaxTextSizeForBitmap(bitmap, text, roundTarget, minOf(width,height).toFloat(), top, bold) * resizeFactor + val maxTextSize = calcMaxTextSizeForBitmap(bitmap, text, roundTarget, minOf(width,height).toFloat(), top, bold, useTallFont) * minOf(1F, resizeFactor) val canvas = Canvas(bitmap) bitmap.eraseColor(Color.TRANSPARENT) canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) @@ -85,10 +92,12 @@ object BitmapUtils { paint.textSize = maxTextSize paint.textAlign = Paint.Align.CENTER paint.isStrikeThruText = strikeThrough + if (useTallFont) + paint.typeface = Typeface.create(GlucoDataService.context!!.resources.getFont(R.font.opensans), Typeface.BOLD) + else if (bold) + paint.typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD) if(withShadow) paint.setShadowLayer(2F, 0F,0F, Color.BLACK) - if (bold) - paint.typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD) val boundsText = Rect() paint.getTextBounds(text, 0, text.length, boundsText) paint.textSize = minOf( maxTextSize, (maxTextSize - 1) * bitmap.width / boundsText.width() ) @@ -111,7 +120,7 @@ object BitmapUtils { else canvas.height/2f + boundsText.height()/2f - boundsText.bottom - Log.d(LOG_ID, "Create bitmap for $text - y: $y - text-size: ${paint.textSize} - color: color - shadow: $withShadow") + Log.d(LOG_ID, "Create bitmap ($width x $height) for $text - y: $y - text-size: ${paint.textSize} (max: $maxTextSize) - color: color - shadow: $withShadow") canvas.drawText(text, width.toFloat()/2, y.toFloat(), paint) return bitmap } catch (exc: Exception) { @@ -253,22 +262,22 @@ object BitmapUtils { } } - fun getGlucoseAsBitmap(color: Int? = null, roundTarget: Boolean = false, width: Int = 100, height: Int = 100, resizeFactor: Float = 1F, withShadow: Boolean = false): Bitmap? { + fun getGlucoseAsBitmap(color: Int? = null, roundTarget: Boolean = false, width: Int = 100, height: Int = 100, resizeFactor: Float = 1F, withShadow: Boolean = false, useTallFont: Boolean = false): Bitmap? { return textToBitmap( ReceiveData.getGlucoseAsString(),color ?: ReceiveData.getGlucoseColor(), roundTarget, ReceiveData.isObsoleteShort() && - !ReceiveData.isObsoleteLong(),width, height, resizeFactor = resizeFactor, withShadow = withShadow) + !ReceiveData.isObsoleteLong(),width, height, resizeFactor = resizeFactor, withShadow = withShadow, useTallFont = useTallFont) } - fun getGlucoseAsIcon(color: Int? = null, roundTarget: Boolean = false, width: Int = 100, height: Int = 100, resizeFactor: Float = 1F, withShadow: Boolean = false): Icon { - return Icon.createWithBitmap(getGlucoseAsBitmap(color, roundTarget, width, height, resizeFactor, withShadow)) + fun getGlucoseAsIcon(color: Int? = null, roundTarget: Boolean = false, width: Int = 100, height: Int = 100, resizeFactor: Float = 1F, withShadow: Boolean = false, useTallFont: Boolean = false): Icon { + return Icon.createWithBitmap(getGlucoseAsBitmap(color, roundTarget, width, height, resizeFactor, withShadow, useTallFont)) } - fun getDeltaAsBitmap(color: Int? = null, roundTarget: Boolean = false, width: Int = 100, height: Int = 100, top: Boolean = false): Bitmap? { - return textToBitmap(ReceiveData.getDeltaAsString(),color ?: ReceiveData.getGlucoseColor(true), roundTarget, false, width, height, top = top) + fun getDeltaAsBitmap(color: Int? = null, roundTarget: Boolean = false, width: Int = 100, height: Int = 100, top: Boolean = false, resizeFactor: Float = 1F, useTallFont: Boolean = false): Bitmap? { + return textToBitmap(ReceiveData.getDeltaAsString(),color ?: ReceiveData.getGlucoseColor(true), roundTarget, false, width, height, top = top, resizeFactor = resizeFactor, useTallFont = useTallFont) } - fun getDeltaAsIcon(color: Int? = null, roundTarget: Boolean = false, width: Int = 100, height: Int = 100): Icon { - return Icon.createWithBitmap(getDeltaAsBitmap(color, roundTarget, width, height)) + fun getDeltaAsIcon(color: Int? = null, roundTarget: Boolean = false, width: Int = 100, height: Int = 100, resizeFactor: Float = 1F, useTallFont: Boolean = false): Icon { + return Icon.createWithBitmap(getDeltaAsBitmap(color, roundTarget, width, height, resizeFactor = resizeFactor, useTallFont = useTallFont)) } fun getRateAsBitmap( diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/utils/HttpRequest.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/HttpRequest.kt index d45e9a4ae..887fcabc8 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/utils/HttpRequest.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/HttpRequest.kt @@ -84,14 +84,14 @@ class HttpRequest { } fun get(url: String, header: MutableMap? = null, trustAllCertificates: Boolean = false): Int { - return request(url, header, null, trustAllCertificates) + return request(url, header, null, trustAllCertificates, false) } - fun post(url: String, postData: String, header: MutableMap? = null, trustAllCertificates: Boolean = false): Int { - return request(url, header, postData, trustAllCertificates) + fun post(url: String, postData: String?, header: MutableMap? = null, trustAllCertificates: Boolean = false): Int { + return request(url, header, postData, trustAllCertificates, true) } - private fun request(url: String, header: MutableMap?, postData: String?, trustAllCertificates: Boolean): Int = runBlocking { + private fun request(url: String, header: MutableMap?, postData: String?, trustAllCertificates: Boolean, postRequest: Boolean): Int = runBlocking { scope.async { reset() val urlConnection = URL(url).openConnection() @@ -110,17 +110,21 @@ class HttpRequest { httpURLConnection!!.doInput = true httpURLConnection!!.connectTimeout = 10000 httpURLConnection!!.readTimeout = 20000 - if (postData == null) { + if (!postRequest) { Log.i(LOG_ID, "Send GET request to ${httpURLConnection!!.url}") httpURLConnection!!.requestMethod = "GET" httpURLConnection!!.doOutput = false } else { Log.i(LOG_ID, "Send POST request to ${httpURLConnection!!.url}") httpURLConnection!!.requestMethod = "POST" - httpURLConnection!!.doOutput = true - val dataOutputStream = DataOutputStream(httpURLConnection!!.outputStream) - val bytes: ByteArray = postData.toByteArray() - dataOutputStream.write(bytes, 0, bytes.size) + if (postData != null) { + httpURLConnection!!.doOutput = true + val dataOutputStream = DataOutputStream(httpURLConnection!!.outputStream) + val bytes: ByteArray = postData.toByteArray() + dataOutputStream.write(bytes, 0, bytes.size) + } else { + httpURLConnection!!.doOutput = false + } } handleResponse() lastCode diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/utils/Utils.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/Utils.kt index 87286bc60..2b2532896 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/utils/Utils.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/Utils.kt @@ -2,6 +2,7 @@ package de.michelinside.glucodatahandler.common.utils import android.accessibilityservice.AccessibilityServiceInfo import android.annotation.SuppressLint +import android.app.AlarmManager import android.content.Context import android.content.pm.PackageManager import android.net.Uri @@ -25,10 +26,12 @@ import java.io.ObjectOutputStream import java.io.OutputStream import java.math.RoundingMode import java.security.MessageDigest +import java.text.DateFormat import java.time.LocalDateTime import java.time.LocalTime import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit +import java.util.Date import java.util.concurrent.TimeUnit import kotlin.math.max @@ -107,12 +110,12 @@ object Utils { ).toInt() } - fun spToPx(sp: Float, context: Context): Float { + fun spToPx(sp: Float, context: Context): Int { return TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, sp, context.resources.displayMetrics - ) + ).toInt() } fun bytesToBundle(bytes: ByteArray): Bundle? { @@ -134,6 +137,7 @@ object Utils { return bytes } + @Suppress("DEPRECATION") fun dumpBundle(bundle: Bundle?): String { try { if (bundle == null) { @@ -444,9 +448,23 @@ object Utils { return false } + fun getElapsedTimeMinute(time: Long, roundingMode: RoundingMode = RoundingMode.DOWN): Long { + return round((System.currentTimeMillis()-time).toFloat()/60000, 0, roundingMode).toLong() + } + + fun getUiTimeStamp(time: Long): String { + if(getElapsedTimeMinute(time) >= (60*24)) + return DateFormat.getDateTimeInstance().format(Date(time)) + return getTimeStamp(time) + } + + fun getTimeStamp(time: Long): String { + return DateFormat.getTimeInstance(DateFormat.DEFAULT).format(Date(time)) + } + fun Context.isScreenReaderOn():Boolean{ val am = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager - if (am != null && am.isEnabled) { + if (am.isEnabled) { val serviceInfoList = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN) if (serviceInfoList.isNotEmpty()) @@ -455,4 +473,12 @@ object Utils { return false } + fun canScheduleExactAlarms(context: Context): Boolean { + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + return alarmManager.canScheduleExactAlarms() + } + return true + } + } \ No newline at end of file diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/utils/WakeLockHelper.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/WakeLockHelper.kt index 4e43d3b9f..5c5c92465 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/utils/WakeLockHelper.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/WakeLockHelper.kt @@ -7,37 +7,59 @@ import java.io.Closeable class WakeLockHelper(val context: Context) : Closeable { private val WAKE_LOCK_TIMEOUT = 10000L // 10 seconds - private val LOG_ID = "GDH.Utils.WakeLockHelper" - private var wakeLock: PowerManager.WakeLock? = null + + companion object { + private var wakeLock: PowerManager.WakeLock? = null + private var wackLockCount = 0 + private val lock = Object() + } + init { - Log.v(LOG_ID, "init called") - wakeLock = - (context.getSystemService(Context.POWER_SERVICE) as PowerManager).run { - newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "GlucoDataHandler::WakeLockHelperTag").apply { - acquire(WAKE_LOCK_TIMEOUT) + try { + synchronized(lock) { + Log.v(LOG_ID, "init called - count: $wackLockCount") + wackLockCount++ + if(wackLockCount == 1 && wakeLock == null) { + wakeLock = + (context.getSystemService(Context.POWER_SERVICE) as PowerManager).run { + newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "GlucoDataHandler::WakeLockHelperTag").apply { + acquire(WAKE_LOCK_TIMEOUT) + } + } + Log.d(LOG_ID, "wakelock acquired: " + active()) + } } + } catch (exc: Exception) { + Log.e(LOG_ID, "init exception: " + exc.toString()) } - Log.d(LOG_ID, "wakelock acquired: " + active()) } - fun active(): Boolean { + private fun active(): Boolean { if (wakeLock != null) { return wakeLock!!.isHeld } return false } - fun release() { - Log.v(LOG_ID, "release called - active: " + active()) - if(active()) { - Log.d(LOG_ID, "wakelock release") - wakeLock?.release() - wakeLock = null + private fun release() { + synchronized(lock) { + Log.v(LOG_ID, "release called - active: " + active() + " count: $wackLockCount") + wackLockCount-- + if(wackLockCount == 0 && active()) { + Log.d(LOG_ID, "wakelock release") + wakeLock?.release() + wakeLock = null + } } } + override fun close() { - Log.v(LOG_ID, "close called") - release() + try { + Log.v(LOG_ID, "close called") + release() + } catch (exc: Exception) { + Log.e(LOG_ID, "init exception: " + exc.toString()) + } } } \ No newline at end of file diff --git a/common/src/main/res/font/opensans.ttf b/common/src/main/res/font/opensans.ttf new file mode 100644 index 000000000..dd6faa8de Binary files /dev/null and b/common/src/main/res/font/opensans.ttf differ diff --git a/common/src/main/res/values-bg/strings.xml b/common/src/main/res/values-bg/strings.xml new file mode 100644 index 000000000..215e935aa --- /dev/null +++ b/common/src/main/res/values-bg/strings.xml @@ -0,0 +1,718 @@ + + + bg + + Сензор + Стойност + Суров + Делта + Скорост + Време на измерване + IOB/COB време + Разлика във времето + Алармa + Източник + на минута + Все още не са получени данни!\nМоля, първо конфигурирайте източник. + + %1$d мин + \> 1ч + + Нараства много бързо + Нараства бързо + Нараства + постоянен + Пада + Пада бързо + Пада много бързо + + ОК + Отказ + + - + Много ниска + Ниска + Висока + Много висока + + + + Оптимизацията на батерията е активирана. + Източник: не е активен + Носимо: свързано (%1$s) + Носимо: изключено + Android Auto: свързано + Android Auto: изключено + Достъпността с висок контраст е активна!\nНатиснете тук, за да отидете на настройки. + Няма нова стойност от %1$d минути! + + Настройки + Източници + GitHub + Актуализации + Поддръжка + Помощ + + + + Нова стойност на глюкозата + Остаряла стойност + Алармa за глюкоза + Състояние на връзката с носимото + Състояние на връзката с Android Auto + Промяна в диапазона на глюкозата + + + + Телефон + Носимо + + Не е активен + В процес + ОК + Няма нова стойност + Няма интернет връзка + Грешка + + Интервал + Интервал за заявка на данни от източника. + Забавяне + Забавяне в секунди преди заявка на данни от облака, защото е необходимо известно време, докато данните се появят там. + + LibreLinkUp + Активиране + Активирайте LibreLinkUp follower, ако потребителското име и паролата са зададени. + Имейл + Имейл за вход в LibreLinkUp + Парола + Парола за LibreLinkUp + Повторно свързване + При следващия интервал ще се извърши ново влизане. + + Nightscout + Активиране + Активирайте Nightscout follower. + URL адрес на Nightscout + URL адрес на Nightscout сървъра. + API тайна + API тайна за достъп до данните (по избор). + Токен за достъп + Добавете вашия токен за достъп, ако е необходим (по избор). + + Винаги + 1 минута + 5 минути + 10 минути + 15 минути + 20 минути + 30 минути + Само при аларма + + + деактивиран + не е свързан + свързан + грешка: %s + + + + + Общи + Известия + Джаджи + Android Auto + Препращане на стойности на глюкозата + Целеви диапазон + Цветове + + Фиктивни стойности + Много висока глюкоза + Граница за много високи стойности на глюкозата. + Висока глюкоза + Граница за високи стойности на глюкозата. + Горна граница на целевия диапазон + Горна стойност за целевия диапазон. + Долна граница на целевия диапазон + Долна стойност за целевия диапазон. + Много ниска глюкоза + Граница за много ниски стойности на глюкозата. + Ниска глюкоза + Граница за ниски стойности на глюкозата. + Известия за Android Auto + Известия за Android Auto. + Интервал на известията + Минимален интервал за показване на известия за нова стойност.\nАлармите винаги ще показват известие. + Само аларма + Ще задейства известия само за аларми в техния специфичен интервал.\nМного ниските стойности винаги ще показват известие. + Фалшив медиен плейър + Показване на стойността на глюкозата като текуща песен за Android Auto. + Изпращане на GlucoData broadcast + Изпращане на GlucoData broadcast за нови стойности, получени от който и да е източник. + Идентифициране на GlucoData приемници + Изберете приемници, към които да се изпраща GlucoData broadcast. Глобалният broadcast ще се изпраща до всяко приложение, което се регистрира динамично за broadcast. + Изпращане на patched libre broadcast + Препращане на стойности на глюкозата към xDrip+. Изберете \'Libre (patched app)\' като източник в xDrip+. + Идентифициране на patched libre приемници + Изберете приемници, към които да се изпраща patched libre broadcast. Глобалният broadcast ще се изпраща до всяко приложение, което се регистрира динамично за broadcast. + Изпращане на xDrip+ broadcast + Изпращане на broadcast, както го изпраща xDrip+. Например към AAPS. + Идентифициране на xDrip+ broadcast приемници + Изберете приемници, към които да се изпраща xDrip+ broadcast. Глобалният broadcast ще се изпраща до всяко приложение, което се регистрира динамично за broadcast. + Използване на mmol/l + Използване на mmol/l вместо mg/dl. + Делта за 5 минути + Включете това, за да използвате делта стойности за интервал от 5 минути, в противен случай се използва интервал от 1 минута. + Цвят за много висока/ниска стойност + Цвят за много високи и много ниски стойности на глюкозата. + Цвят за висока/ниска стойност + Цвят за високи и ниски стойности на глюкозата. + Цвят за целевия диапазон + Цвят за стойности на глюкозата в целевия диапазон. + Известие + Показва постоянно известие с текущата стрелка за тенденция, стойност на глюкозата и делта в лентата на състоянието.\nТова известие предотвратява спирането на това приложение от Android. + Икона в лентата на състоянието + Стил на малката икона в лентата на състоянието на известието. + Голяма икона в лентата на състоянието + По-голяма икона в лентата на състоянието. Моля, деактивирайте тази настройка, ако иконата е твърде голяма и е отрязана. + Скриване на съдържанието + Това ще премахне съдържанието на известието, за да има само малка икона с него. + 2. известие + Показване на известие без съдържание, за да има допълнителна икона в лентата на състоянието. + 2. икона в лентата на състоянието + Стил на малката икона в лентата на състоянието на известието. + 3. известие + 3. икона в лентата на състоянието + Плаващ джаджа + Показване на глюкоза, тенденция, делта, време и IOB/COB като плаващ джаджа.\nАко задържите и не местите джаджата за повече от 5 секунди, тя ще изчезне. + Размер + Промяна на размера на плаващия джаджа + Стил + Стил на плаващия джаджа. + Прозрачност + Прозрачност на фона за плаващия джаджа. + Прозрачност на джаджата + Прозрачност на фона за джаджи. + Относително време + Използване на относително време вместо времева марка на глюкозата за джаджи.\nОтносителното време зависи от настройките на батерията на вашето устройство и може да не работи правилно. + + Икона на приложението + Стойност на глюкозата + Стрелка за тенденция + Делта + + Носимо: вибрация + + Глобален broadcast + + Показване на всички + Не е намерен приемник! + + Глюкоза + Глюкоза и тенденция + Глюкоза, тенденция и делта + Глюкоза, тенденция, делта и времева марка + Глюкоза, тенденция, делта, време и IOB/COB + + Поддръжка на BangleJS + Препращане на стойности към widbgjs джаджа за BangleJS. + + + + Получаване на данни за усложнения + + Телефон: свързан (%1$s) + Телефон: изключен + + На преден план + Голяма стрелка за тенденция + Цветен AOD + + + + Глюкоза + Глюкоза (много голяма и цветна) + Глюкоза (цветна) + Глюкоза (голяма) + Глюкоза с икона + Глюкоза и тенденция + Глюкоза и тенденция (голяма и цветна) + Глюкоза и тенденция (голяма) + Глюкоза и тенденция (цветна) + Глюкоза и диапазон на тенденцията (ако се поддържа) + Глюкоза, делта и диапазон на тенденцията (ако се поддържа) + Делта, стрелка за тенденция и диапазон на тенденцията (ако се поддържа) + Глюкоза и делта + Глюкоза, делта и тенденция + Глюкоза, делта, тенденция и времева марка + Глюкоза, икона за тенденция, делта и времева марка + Глюкоза и стойност на тенденцията + Делта (голяма) + Делта (голяма, цветна) + Делта + Делта и тенденция + Делта с икона + Делта и времева марка + Делта и времева марка (цветна) + Делта и времева марка (голяма) + Тенденция (голяма, цветна) + Тенденция (цветна) + Икона за тенденция + Тестов диапазон на усложнения с отрицателни стойности (за поддръжка на диапазон на тенденцията) + Стойност на тенденцията + Стойност на тенденцията и стрелка + Стойност на тенденцията с икона + Времева марка на глюкозата + Времева марка + Времева марка (цветна) + Времева марка (голяма) + Ниво на батерията (часовник и телефон) + Ниво на батерията на часовника + Ниво на батерията на телефона + IOB/COB + + + + https://github.com/pachi81/GlucoDataAuto/blob/main/README.md + За да използвате това приложение в Android Auto, трябва да активирате настройките за разработчици в Android Auto, като щракнете няколко пъти върху версията. След това трябва да активирате \"Неизвестни източници\" в менюто с настройки за разработчици. + Информация + Ако използвате GlucoDataHandler като източник, тези настройки ще бъдат презаписани от GlucoDataHandler, така че не е нужно да ги променяте тук. + GlucoDataAuto липсва + За да използвате GlucoDataHandler за Android Auto, трябва да инсталирате GlucoDataAuto от GitHub.\nМоля, щракнете тук за повече информация и за да изтеглите GlucoDataAuto. + Препращане към GlucoDataAuto + Изпращане на стойности на глюкозата и настройки към GlucoDataAuto, ако телефонът е свързан с Android Auto. + + + + + Известие на преден план + Известие на преден план, което предотвратява спирането на приложението, докато работи във фонов режим. + Второ известие + Трето известие + Допълнително известие за показване на допълнителна икона в лентата на състоянието. + Известие за работник + Известие за работник, показано по време на изпълнение на задачи във фонов режим. + Известие на преден план + Известие на преден план, което предотвратява спирането на приложението, докато работи във фонов режим. + Известие за Android Auto + Известие, което се показва в Android Auto + Noise-Block е активен в настройките за между приложения на xDrip+!\nМоля, променете го на \"Изключително шумно\"! + Облачни услуги + Juggluco + За да получавате стойности от Juggluco:\n- отворете Juggluco\n- отидете на \"Настройки\"\n- активирайте \"Glucodata broadcast\"\n- активирайте \"de.michelinside.glucodatahandler\" + За да получавате стойности от Juggluco:\n- отворете Juggluco\n- отидете на \"Настройки\"\n- активирайте \"Glucodata broadcast\"\n- активирайте \"de.michelinside.glucodataauto\" + xDrip+ + За да получавате стойности от xDrip+:\n- отворете xDrip+\n- отидете на настройки\n- отидете на настройки за между приложения\n- активирайте \"Broadcast locally\"\n- задайте \"Noise Blocking\" на \"Send even Extremely noisy signals\" + - активирайте \"Compatible Broadcast\" - проверете дали \"Identify receiver\" е празно или ако вече съществува запис, добавете \"de.michelinside.glucodatahandler\", разделено с интервал + - активирайте \"Compatible Broadcast\"\n- проверете дали \"Identify receivers\" е празно или добавете нов ред с \"de.michelinside.glucodataauto\" + Настройка на LibreLinkUp + ВАЖНО: това не е акаунтът, използван за приложението FreeStyle Libre!\nЗа да активирате LibreLinkUp follower:\n- отворете приложението FreeStyle Libre и изберете в менюто Споделяне или Свързани приложения\n- активирайте LibreLinkUp връзката\n- инсталирайте LibreLinkUp от Play Store\n- настройте акаунта си и изчакайте поканата + Контакт + Поддръжка на IOB/COB + Получаване и на IOB и COB стойности от nightscout pebble endpoint. + Известието е задължително за правилното функциониране на приложението.\nЗапочвайки с Android 13, можете да плъзнете това известие, за да го премахнете. Ако не искате известието да се връща, задайте \"Икона в лентата на състоянието\" на \"Икона на приложението\" и активирайте \"Скриване на съдържанието\". + AAPS + AndroidAPS + За да получавате стойности от AAPS:\n- отворете приложението AAPS\n- отидете на \"Config Builder\"\n- активирайте \"Samsung Tizen\" или \"Data Broadcaster\" + WatchDrip+ + "Активиране на комуникацията с WatchDrip+ (без графика).\nВАЖНО: активирайте \"Enable service\" в WatchDrip+ след активиране на тази настройка тук!" + Повторно появяване + Интервал, през който известието ще се появява отново, ако няма нова стойност. (0 за никога). + Стил на иконата/изображението на фиктивния медиен плейър. + Стил на иконата/изображението + Мобилен + Носимо + Запазване на логове + Логовете са запазени успешно + Неуспешно запазване на логове! + Логовете от носимото са запазени успешно + Неуспешно запазване на логове от носимото! + За всички задачи, свързани с време/интервал, това приложение изисква разрешение за аларми и напомняния.\nТо няма да добавя или променя никакви напомняния на потребителя, а е само за вътрешно планиране.\nАко натиснете OK, ще бъдете пренасочени към настройката за разрешение, за да го активирате за GlucoDataHandler. + Разрешение за аларми и напомняния + Планирането на точна аларма е деактивирано!!!\nGlucoDataHandler може да не работи правилно!!!\nНатиснете тук, за да отидете директно на настройката за разрешение. + + Пациент + Ако има повече от един пациент, свързан с акаунта на libre, моля, изберете пациента, за когото се получават данните. + Тапет на заключения екран + Активиране + Ако активирате тапета на заключения екран, приложението ще замени тапета на заключения екран. + Вертикална позиция + Вертикална позиция на изображението на глюкозата и тенденцията на заключения екран:\n0 е горната част на дисплея\n100 е долната част на дисплея + Показването на известия е деактивирано!!!\nGlucoDataHandler може да не работи правилно и не може да показва аларми!!!\nНатиснете тук, за да отидете директно на настройката за разрешение. + + Липсват данни! Моля, първо приемете поканата в приложението LibreLinkUp! + Цветна икона в лентата на състоянието + Ако вашето устройство поддържа цветни икони в лентата на състоянието, можете също да го деактивирате, за да използвате бял елемент. + + Аларми + Интервал + Минимален интервал в минути между известията за този тип аларма.\nИнформация: ще има ново известие след изтичане на времето само ако делтата е отрицателна. + Минимален интервал в минути между известията за този тип аларма.\nИнформация: ще има ново известие след изтичане на времето само ако делтата е положителна. + Време в минути от получаването на последната стойност на глюкозата, за да се задейства това известие. То ще се появява отново през този интервал. + Активирано + Известията са активирани за този тип аларма. + Известията са деактивирани за този тип аларма. + Тестване + Натиснете тук, за да задействате известие след 3 секунди за този тип аларма. + Настройки + Натиснете тук, за да отидете на настройките за канала за известия за тази аларма.\nТам можете да промените всички свързани настройки. + Запазване на звука + Натиснете тук, ако искате да запазите звука по подразбиране за алармата във файл на телефона си. + Алармата е запазена! + Отлагане + Всички аларми + Известия за аларма + Носимо: Известия за аларма + Известията за аларма са активни на телефона.\nЗабележка: алармите използват силата на звука за аларма на телефона. + Известията за аларма са неактивни на телефона. + Спиране + Много ниска! + Ниска! + Висока! + Много висока! + Аларма за много ниска стойност + Известие за аларми за много ниска стойност. + Аларма за ниска стойност + Известие за аларми за ниска стойност. + Аларма за висока стойност + Известие за аларми за висока стойност. + Аларма за много висока стойност + Известие за аларми за много висока стойност. + Отхвърляне + Тестова аларма! + Категории на алармите + Известие на цял екран + Активира известие на цял екран на заключения екран.\nТова може да изисква допълнително разрешение. + Отхвърляне на заключване + Моля, активирайте тази настройка, ако имате проблеми с известието на цял екран.\nНа някои устройства това ще доведе до отключване на екрана, докато се задейства известието на цял екран.\nТака че, моля, не го активирайте, ако нямате проблеми. + Принудително възпроизвеждане на звук за аларма + Това ще принуди възпроизвеждане на звук и вибрация за аларма, дори ако телефонът е в безшумен режим или режим на вибрация. Винаги ще спира режима \"Не ме безпокойте\"!\nЗа да активирате тази настройка, може да бъдете помолени да разрешите на това приложение да променя режима \"Не ме безпокойте\". + Носимо: Принудително възпроизвеждане на звук за аларма + Принудителна вибрация + Принудителна вибрация, дори ако телефонът е в безшумен режим. Винаги ще спира режима \"Не ме безпокойте\"!\nЗа да активирате тази настройка, може да бъдете помолени да разрешите на това приложение да променя режима \"Не ме безпокойте\". + Ниво на звука + Принудително задаване на ниво на звука за алармата.\n-1 за използване на настройките по подразбиране на телефона. + Разширени + Разширени настройки за аларма за известие на цял екран и звуци. + Известие + Звук + Настройки за аларма за стойности на глюкозата, започващи от %1$s. + Без аларма, докато носимото е свързано + Няма да има известие за аларма на телефона, ако телефонът е свързан с GlucoDataHandler на часовника. + Ще има известие за аларма на телефона, дори ако телефонът е свързан с GlucoDataHandler на часовника. + Без аларма, докато Android Auto е свързан + Няма да има известие за аларма на телефона, ако Android Auto е свързан. + Ще има известие за аларма на телефона, дори ако Android Auto е свързан. + Повторно задействане + Ако известието за аларма не е затворено, алармата ще се задейства отново след този интервал в минути до 3 пъти.\n0 - без повторно задействане + Остаряла + Аларма за остаряла стойност + Аларма, ако няма нова стойност на глюкозата за %1$s минути. + Това известие ще се появи, ако няма нова стойност в рамките на интервал. + Няма нова стойност! + + Показването на известия е деактивирано!!! + GlucoDataHandler се нуждае от известия, за да изпълнява задачи във фонов режим и да показва аларми. + Известие за аларма + Известия за аларма. Моля, не променяйте настройките, тъй като приложението осигурява звук и вибрация за известията вътрешно. + Избор на персонализиран звук + Безшумно - без звук + Използване на персонализиран звук + Използване на персонализирано избран звук. + Използване на звук, предоставен от приложението. + Забавяне на звука + Време в секунди преди възпроизвеждане на звука.\nЩе вибрира през цялото време, дори ако не е избран звук. + Звук и вибрация + Само вибрация + Носимо: Само вибрация + Известието е деактивирано! + За да показва аларми, GlucoDataHandler се нуждае от активиран канал за известия за аларма.\nЩе бъдете пренасочени към настройката за известия за това приложение.\nМоля, активирайте канала за известия за аларма там. + Вътрешен звук на приложението + Включване на настройки + Включване на настройки в съобщението до GlucoDataAuto.\nТова ще презапише настройките в GlucoDataAuto. + За да получавате стойности от GlucoDataHandler:\n- отворете GlucoDataHandler\n- отидете на настройки\n- активирайте \"Препращане към GlucoDataAuto\"\n- за да използвате същите настройки, активирайте \"Включване на настройки\" + Ако използвате известия за Android Auto, алармите за много ниска стойност винаги ще задействат известие и това не може да бъде деактивирано или променено. + IOB от Juggluco + За да получавате IOB от Juggluco:\n- отворете Juggluco\n- отидете на \"Настройки\"\n- активирайте \"IOB\"\n- отидете на \"Web server\"\n- активирайте \"Active\"\n- по избор: активирайте \"Local only\" + IOB от xDrip+ + За да получавате стойности от xDrip+:\n- отворете xDrip+\n- отидете на настройки\n- отидете на настройки за между приложения\n- активирайте \"xDrip Web Service\" + Активиране на използването на IOB + Натиснете тук, за да зададете настройките на nightscout да използват локалния уеб сървър за получаване на допълнителни IOB стойности. + Презаписване на nightscout + Това ще презапише текущите ви настройки за източник на nightscout, за да използвате локален уеб сървър на порт 17580 за получаване на IOB стойности.\nСигурни ли сте? + Превключване на известия + Известията са включени + Известията са изключени + Натиснете play, за да превключите известието + Настройките за аларма са свързани само с известията на Android Auto и не задействат никакъв звук. + Общи настройки, като например единица, време и настройки за делта. + Настройки за границите на глюкозата и съответните цветове. + Настройки за джаджи на телефона и плаваща джаджа. + Настройки за известия. + Настройки за замяна на тапета на заключения екран. + Настройка за GlucoDataAuto за използване с Android Auto. + Часовници MiBand и Amazfit + Часовници Wear OS + Часовник + Настройки за свързани смарт часовници. + Настройки за прехвърляне на стойности на глюкозата към други приложения. + Прехвърляне на стойности + Визуализация + Часовници BangleJS + За да използвате GlucoDataHandler на часовници Wear OS, моля, инсталирайте го от Google Play Store на часовника. + Проверка на връзката + Натиснете тук, за да проверите отново връзката с Wear OS.\nМожете също да задействате тази проверка, като натиснете информацията за връзката на началния екран (дори в приложението Wear OS). + WatchDrip+ поддържа няколко устройства MiBand и Amazfit.\nЗа да получите пълен списък с поддържаните устройства и повече информация за конфигуриране на WatchDrip+, моля, натиснете тук. + Само вибрация без известие не работи на всяко устройство! + Използване на звук за аларма + Използване на силата на звука за аларма вместо звука за звънене.\nТова може да е независимо от режима на вибрация. + Активирайте това, за да принудите да не се възпроизвежда звук за аларма, независимо от други настройки за звук на алармата. + Забавяне на стартирането + Забавяне преди първото възпроизвеждане на вибрация и звук.\nПонякога известието от телефона спира вибрацията веднага. + Цвят за остаряла стойност + Цвят за липса на нови стойности на глюкозата. + Без изскачащ прозорец, докато телефонът е свързан + Без допълнителен изскачащ прозорец за известие на часовника, докато телефонът е свързан.\nВибрацията и звукът ще се задействат, както е конфигурирано. + Подробности + Връзки + изключен + свързан + Цветова схема на приложението + Използване на системната тема + Светла тема + Тъмна тема + Действие при докосване + Изберете действие или приложение, което да се изпълни при докосване на този елемент. + Без действие + Превключване на плаващата джаджа + Действие при докосване на усложнение + Моля, деактивирайте тази настройка, ако стрелката за тенденция е твърде голяма или е отрязана. + Потребителски интерфейс + Общи настройки за потребителския интерфейс на това приложение. + GlucoDataAuto работи + Активирайте го само ако използвате източник на последовател и само известия за Android Auto.\nТова предотвратява затварянето на това приложение от Android, докато не е свързано с Android Auto, така че приложението не може да разпознае връзката с Android Auto.\nВместо да активирате тази настройка, можете също да отворите приложението в Android Auto веднъж, ако сте свързани. + Оптимизацията на батерията е активирана!\nНатиснете тук, за да я деактивирате. + Ако използвате източник на последовател, приложението може да не се стартира, докато е свързано с Android Auto, ако използвате само известия за Android Auto.\nЗа да сте сигурни, че приложението открива връзката с Android Auto, трябва да деактивирате оптимизацията на батерията.\nАко все още не работи, можете също да активирате режима на преден план или трябва да отворите приложението в Android Auto веднъж, ако е свързано с Android Auto. + Настройка на Android Auto + Android Auto е или отделно приложение, или част от системата и може да бъде достъпно чрез настройките на Android.\nЗа да активирате GlucoDataAuto за Android Auto, трябва да изпълните следните стъпки: + 1. Активиране на режим за разработчици + - отворете Android Auto\n- превъртете надолу до Версия\n- докоснете няколко пъти Версията, докато се появи изскачащ прозорец, за да \"Разрешите настройките за разработчици\"\n- натиснете \"OK\" + 2. Активиране на \"Неизвестни източници\" + - отворете Android Auto\n- отворете в менюто с 3 точки \"Настройки за разработчици\"\n- превъртете надолу до \"Неизвестен източник\"\n- активирайте го + 3. Задаване на настройки за известия + - отворете Android Auto\n- превъртете надолу до \"Съобщения\"\n- активирайте \"Показване на известия за съобщения\"\n- активирайте \"Показване на първия ред на съобщенията\" + 4. Активиране на GlucoDataAuto + - отворете Android Auto\n- превъртете надолу до \"Дисплей\"\n- отворете \"Персонализиране на стартера\"\n- активирайте \"GlucoDataAuto\"\nАко GlucoDataAuto не е наличен, моля, рестартирайте телефона си. + Цветен оформление + Показва стойностите с голям и цветен текст.\nНа някои устройства на Samsung с Android 13 и по-нова версия това може да доведе до срив на приложението поради грешка от страна на Samsung.\nАко проблемът възникне, тази настройка ще бъде автоматично деактивирана, за да се гарантира, че приложението остава функционално. + Открит е срив! + Съжалявам, че приложението се срина по време на изпълнение!\nМоля, изпратете ми логовете, за да мога да анализирам грешката и да се опитам да я поправя възможно най-скоро.\nБлагодаря ви! + Dexcom Share + Акаунт в САЩ + Използва се акаунт в САЩ. + Използва се международен акаунт. + Активиране на акаунт в Dexcom Share. Трябва да зададете потребителско име и парола преди това. + Потребителско име + Потребителско име, имейл или телефонен номер за вход в акаунта ви в Dexcom Share.\nВАЖНО: ако използвате телефонен номер, той трябва да включва кода на държавата (напр. +1 за САЩ). + Парола за акаунт в Dexcom Share. + Настройка на Dexcom Share + Липсва разрешение! + Настройката \'%1$s\' е зададена, но съответното разрешение не е предоставено! Натиснете \"OK\", за да бъдете пренасочени към диалоговия прозорец за разрешения, или натиснете \"Отказ\", за да деактивирате тази настройка. + Broadcast Service API + Използвайте xDrip+ Broadcast Service API, за да получавате данни (също IOB) от xDrip+.\nМоля, първо активирайте \"Broadcast Service API\" в \"Настройки за между приложения\" в xDrip+! + Време за остаряване + Време в минути, след което стойността се третира като остаряла.\nСлед два пъти това време стойността се премахва. + Локални приложения + Приложения на същото устройство, предоставящи стойности на глюкозата като вътрешно broadcast съобщение, като Juggluco, xDrip+, AAPS и т.н. + Облачни услуги, предоставящи стойности на глюкозата през интернет, като LibreLinkUp, Dexcom Share и Nightscout. + Алтернатива: Broadcast локално + За да получавате стойности от Dexcom BYODA, трябва да активирате broadcast към AAPS, докато изграждате приложението. + За да получавате стойности от Eversense, трябва да използвате ESEL или в режим на компаньон (четене на известия), или свързан с patched приложението Eversense.\nЗа повече информация относно ESEL, натиснете тук. + За да получавате стойности от Dexcom BYODA, трябва да активирате broadcast към xDrip+, докато изграждате приложението. + Версия %s е налична + Бележки + Известията са деактивирани! \nНатиснете тук, за да ги активирате. + Показване на друга единица + Показване на стойността на глюкозата в %s в детайлите. + Друга единица + Натиснете тук, за да проверите данните за вашия акаунт в Dexcom Share за вашия акаунт извън САЩ. + Натиснете тук, за да проверите данните за вашия акаунт в Dexcom Share за вашия акаунт в САЩ. + Промяна на размера на данните на заключения екран. + Нулиране на връзката + Натиснете тук, за да нулирате връзката между часовника и телефона, ако не работи както се очаква.\nАко нулирането не реши проблема, моля, рестартирайте часовника си. + Връзката не работи както се очаква! Моля, опитайте да рестартирате часовника си. + Време за затваряне + Време в секунди за затваряне на плаващата джаджа, докато натискате джаджата, без да я местите.\n0 за деактивиране на тази настройка. + Активиране на тихи часове + Няма известия за аларма за тази аларма по време на тихи часове.\nКонфигурирайте предпочитаните от вас времена, за да използвате тази функция. + Няма известия за аларма за всички аларми по време на тихи часове.\nКонфигурирайте предпочитаните от вас времена, за да използвате тази функция. + Начален час + Изберете часа, в който искате да започнат вашите тихи часове. + Краен час + Изберете часа, в който искате да завършат вашите тихи часове. + Дни за тихи часове + Посочете дните от седмицата, в които тихите часове трябва да започнат в избрания начален час. + + Приложението се срива поради цветното оформление на известията!\nТо е деактивирано, за да се предотврати този срив.\nРестартирането на телефона обикновено оправя това. + Автоматично затваряне на известието + Автоматично затваряне на известието за аларма след приключване на звука и вибрацията, но само след поне 30 секунди.\nАко повторното задействане е активно, известието ще бъде затворено, ако няма допълнително задействане. + + Настройките са запазени успешно + Неуспешно запазване на настройките! + Настройките са прочетени успешно + Неуспешно четене на настройките! + Експортиране / Импортиране + Експортиране или импортиране на настройки и запазване на логове. + Експортиране + Натиснете тук, за да експортирате всички настройки във файл. \nВАЖНО: това ще експортира само променените настройки! Настройките със стойности по подразбиране няма да бъдат част от експорта. + Импортиране + Натиснете тук, за да импортирате настройки от файл.\nВАЖНО: това няма да пресъздаде настройките по подразбиране, тъй като те не са част от експорта.\nАко искате да нулирате до настройките по подразбиране, първо изтрийте данните на приложението и след това импортирайте. + + Запазване на логове от телефона във файл. + Запазване на логове от свързания часовник Wear OS във файл. + Повторение + Без вибрация + Режим на вибрация + + Бързо покачване + Аларма за бързо покачване + Аларма, ако стойността на глюкозата е по-висока или равна на %1$s и делтата се покачва с поне +%2$s за %3$d последователни показания. + Бързо покачване! + + Бързо падане + Аларма за бързо падане + Аларма, ако стойността на глюкозата е по-ниска или равна на %1$s и делтата пада с поне -%2$s за %3$d последователни показания. + Бързо падане! + Интензитет на вибрацията + Интензитет на вибрацията + Настройки за аларма + Стойност на делтата, когато алармата трябва да се задейства. + Брой на появяванията + Брой пъти, в които стойността на делтата трябва да бъде достигната последователно. + Граница + Граница за алармата за покачване/падане.\nСтойността на глюкозата трябва да е по-висока/по-ниска, за да се задейства алармата. + на минута + на 5 минути + Други аларми + Разширени настройки за аларма + Вътрешните съобщения от този източник ще бъдат обработени. + Вътрешните съобщения от този източник ще бъдат игнорирани. + Временно деактивирано + Временно деактивирано до + Отложено до + + + Тенденция + %1$d градуса нагоре + %1$d градуса надолу + + Аларми: %1$s + неактивни + отложени до %1$s + временно деактивирани до %1$s + + активирано + деактивирано + + не е налично + + Произнасяне на стойностите + + + Време %d минута + Време %d минути + + Отворете Интеграция и активирайте \"Споделяне на данни с GWatch\"\nЗабележка: broadcast-ът на DiaBox съдържа само стойността на глюкозата и не съдържа повече информация, като например тенденция! + Забележка: broadcast-ът на AAPS от BYODA изглежда не работи с други приложения. \nМоля, използвайте broadcast-а на xDrip+ в BYODA. + + Започнете с GDA на Android Auto + Преобразуването на текст в реч (TTS) не е активирано.\nЗа да използвате тази функция, активирайте TTS в настройките за достъпност на Android.\nСлед като е активирано, докоснете тук, за да актуализирате. + Произнасяне на стойност + Възпроизвеждане + Произнасяне на текущата стойност при възпроизвеждане.\nИзисква активиране на преобразуване на текст в реч. + Възпроизвеждане на празен звук, за да се изведе медийният плейър на преден план на разделения екран. + Произнасяне на нова стойност + Произнасяне на нови стойности в Android Auto, дори когато използвате друг медиен плейър. + Докоснете, за да тествате преобразуването на текст в реч. + Произнасяне само на аларми. + Задайте интервала за произнасяне на нови стойности в Android Auto. + Интервалът за получаване на нови стойности на глюкозата се използва като продължителност за лентата за напредък в медийния плейър.\nЗадаването на стойността на 0 деактивира лентата за напредък. + + Превключване на произнасяне на нова стойност + Произнасянето е включено + Произнасянето е изключено + Активиране на икона за аларма + Активиране на икона за аларма на главния екран за превключване на състоянието на алармата. + + + + Автоматично приемане на нови условия + Като активирате това, вие автоматично приемате новите Условия за ползване, за да осигурите непрекъснато получаване на данни. Няма да бъдете информирани отделно за новите условия. Ако искате да ги прочетете, деактивирайте тази настройка и вижте приложението LibreLinkUp в случай на грешка 4. + GlucoDataHandler може автоматично да приема нови Условия за ползване за LibreLinkUp.\nНатиснете OK, за да активирате тази функция, или Отказ, за да прочетете и ръчно да приемете условията в приложението LibreLinkUp. + + Грешка, моля, влезте отново в приложението LibreLinkUp и проверете за евентуални стъпки, които трябва да се предприемат! + Тип грешка %1$s, моля, влезте отново в приложението LibreLinkUp и проверете за евентуални стъпки, които трябва да се предприемат! + Трябва да се приемат нови Условия за ползване. Активирайте автоматичното приемане в настройките на източника LibreLinkUp или влезте отново в приложението LibreLinkUp и приемете условията. + Неправилно потребителско име или парола. Моля, проверете настройките на източника LibreLinkUp. Ако е необходимо, опитайте да влезете отново или да преинсталирате приложението LibreLinkUp и да проверите настройките на акаунта си.\nМоля, спрете всички други приложения, които имат достъп до същия акаунт! + Последно отчитане на глюкозата на сървъра от %1$s + Не са намерени отчитания на глюкозата на сървъра. + Твърде много заявки! + Моля, спрете всички други приложения, които имат достъп до същия акаунт! + + Очаква се данни + Изпращане на ниво на батерията + Изпращане на ниво на батерията до часовника, за да се показва в усложнението и на главния екран. + Показване на ниво на батерията + Задайте минималното време (в минути) между измерванията на глюкозата, преди да изпратите нова актуализация до часовника.\nАлармите и много ниските измервания винаги ще бъдат изпращани. + Задайте минималното време (в минути) между измерванията на глюкозата, преди да изпратите нова актуализация до приемниците. + Алармата ще звучи и ще вибрира многократно, докато не отхвърлите известието или не се достигне зададената максимална продължителност на алармата. + Максимална продължителност на алармата + Посочете максималното време (в минути), през което алармата ще звучи и ще вибрира. Стойност 0 означава, че алармата ще се повтаря безкрайно. + За да активирате източници, моля, първо ги настройте в приложението за телефона. Активирайте източници на вашия часовник само ако все още не получавате данните на телефона си. + Забележка от разработчика + Това приложение е разработено в свободното ми време като хоби проект. Поради това не мога да гарантирам, че всички функции ще работят безупречно на всяко устройство. Въпреки това, аз постоянно работя по подобрения.\nАко срещнете проблеми или имате въпроси, моля, свържете се с мен директно, преди да оставите отрицателна оценка. \nЗа да сте в течение с важни актуализации и корекции на грешки, можете да се присъедините към групата във Facebook или Google Groups. + + За да получавате данни от сървърите на Dexcom Share:\nВ приложението Dexcom:\n - Създайте последовател\n - Под \"Връзки\" се уверете, че се показва \"Споделяне е включено\" + В GlucoDataHandler:\n - Въведете вашето потребителско име и парола за Dexcom Clarity \n (ако използвате телефонния си номер като потребителско име, включете кода на държавата си)\n - Проверете настройката за акаунт в САЩ\nВажно: Не работи с акаунт на последовател! + + Актуализирано + Аларми + Циферблати за часовници + Информация за циферблати за часовници на трети страни за Wear OS. + Настройка + Циферблати за часовници на трети страни + Това приложение не предоставя циферблат за часовник, а предоставя няколко усложнения (малките кръгли части на циферблата на часовника), които могат да се използват за циферблати за часовници, поддържащи усложнения на трети страни. + Стандартни + Повечето производители на часовници вече предоставят циферблати за часовници, които могат да се използват с усложнението GlucoDataHandler.\nНапример, повечето циферблати за часовници \"Info\" на Samsung са съвместими. + Pujie + Pujie е приложение за създаване на циферблати за часовници с облачна библиотека.\nЗа да намерите съвместими циферблати за часовници, потърсете в библиотеката циферблати за часовници, започващи с \"GDH_\".\nЗабележка: Pujie не е съвместимо с устройства с Wear OS 5.\nДокоснете тук, за да отворите приложението в Play Store. + Diabetic Masked Man + Diabetic Masked Man създава циферблати за часовници на трети страни, специално проектирани за използване с GlucoDataHandler.\nДокоснете тук, за да отворите неговата страница в Play Store. + GDC Watch Faces + GDC Watch Faces са проектирани за използване с GlucoDataHandler.\nДокоснете тук, за да отворите страницата в Play Store. + Винаги актуализиране на усложненията + Винаги изпращайте нови стойности на глюкозата до часовника.\nДеактивирайте това, за да предотвратите актуализации на данни на часовника, докато екранът на часовника е изключен, за да пестите батерия.\nСтойностите на алармата винаги ще се изпращат до часовника.\nВАЖНО: Алармата за остаряване е деактивирана на часовника, когато тази настройка е деактивирана! + Винаги предупреждавайте за много ниска стойност + Тази аларма ще ви уведоми, дори ако отлагането или общите тихи часове за всички аларми са активни, като гарантира, че сте предупредени за много ниски нива на глюкозата. + + Ако тази грешка продължава, опитайте да преинсталирате приложението Libre. Това често решава проблема. + Сървър + По подразбиране (.io) + Руски (.ru) + + Име + Име на потребителя/пациента, което трябва да се показва в известието и по време на говорене.\nТова трябва да се използва, ако използвате множество варианти на GlucoDataAuto, в противен случай може да е празно. + + Видео урок + Щракнете тук, за да отидете на видеоклипа в YouTube, който илюстрира настройката на този източник. Много благодаря на Diabetic Masked Man за създаването на видеоклиповете. + + 30 минути + 60 минути + 90 минути + 120 минути + Отлагане на известието + Изберете до 3 продължителности на отлагане, за да се показват като бутони в известието за аларма. + + Показване на IOB/COB стойности + Показване на IOB и COB стойности, ако са налични. + + diff --git a/common/src/main/res/values-de/strings.xml b/common/src/main/res/values-de/strings.xml index 43449fafa..072c5d681 100644 --- a/common/src/main/res/values-de/strings.xml +++ b/common/src/main/res/values-de/strings.xml @@ -1,5 +1,5 @@ - + de pro Minute @@ -302,7 +302,7 @@ - \"Kompatible Broadcasts\" aktivieren\n- \"Identifiziere Empfänger\" leer lassen oder \"de.michelinside.glucodatahandler\" durch ein Leerzeich getrennt hinzufügen - \"Kompatible Broadcasts\" aktivieren\n- \"Identifiziere Empfänger\" leer lassen oder eine neue Zeile mit \"de.michelinside.glucodataauto\" hinzufügen LibreLinkUp einrichten - WICHTIG: nicht die Zugangsdaten für die FreeStyle Libre App verwenden!\nEinrichtung der LibreLinkUp Verbindung:\n- FreeStyle Libre App öffnen und unter Verbinden auf Teilen oder Verbundene Anwendungen klicken\n- LibreLinkUp aktivieren\n- LibreLinkUp aus dem PlayStore installieren\n- in der LibreLinkUp App einloggen und die Einladung annehmen + WICHTIG: nicht die Zugangsdaten für die FreeStyle Libre App verwenden!\nEinrichtung der LibreLinkUp Verbindung:\n- FreeStyle Libre App öffnen und unter Verbinden auf Teilen oder Verbundene Anwendungen klicken\n- LibreLinkUp aktivieren\n- LibreLinkUp aus dem Play Store installieren\n- in der LibreLinkUp App einloggen und die Einladung annehmen Kontakt IOB/COB Unterstützung Zusätzlich auch IOB und COB Werte vom Nightscout Pebble Endpunkt empfangen. @@ -375,8 +375,6 @@ Alarm Kategorien Vollbildbenachrichtigung Aktiviert die Vollbildbenachrichtigung auf dem Sperrbildschirm.\nBeim Aktivieren wird gegebenenfalls nach der entsprechenden Berechtigung gefragt. - Schlummern - Wenn aktiviert, enthalten die Benachrichtigungen Buttons um Schlummern zu aktivieren. Dismiss keyguard Bitte diese Einstellung nur aktivieren, wenn es Probleme mit der Anzeige der Vollbildbenachrichtigung auf dem Speerbildschirm gibt.\nBei einigen Geräte verursacht diese Einstellung dass immer die PIN-Abfrage vor der Benachrichtigung erscheint. Alarm Ton erzwingen @@ -452,7 +450,7 @@ Glucosewerte weiterleiten Visualisierung BangleJS Uhren - Um GlucoDataHandler auf einer Wear OS Uhr zu verwenden, muss es aus dem Google Playstore auf der Uhr installiert werden.\nDie App stellt kein Ziffernblatt zur Verfügung, sondern Komplications (die kleinen, meist runden Bestandteile eines Ziffernblatts), welche für Ziffernblätter verwendet werden können, die dies unterstützen. + Um GlucoDataHandler auf einer Wear OS Uhr zu verwenden, muss es aus dem Google Play Store auf der Uhr installiert werden. Verbindung prüfen Hier drücken, um im Hintergrund die Verbindung zur Uhr zu Überprüfen.\nDas kann auch durch einen Klick auf die Verbindungsinformation auf der Startseite auf der Uhr und dem Telefon gestartet werden. WatchDrip+ unterstützt verschiedene MiBand und Amazfit Modelle.\nFür eine Liste aller unterstützten Uhren und Informationen über die Einrichtigung zu bekommen, bitte hier klicken. @@ -509,7 +507,6 @@ Benutzername, E-Mail oder Telefonnummer für das Login zum Dexcom Share Konto.\nWICHTIG: die Telefonnummer muss die Länderkennung enthalten, z.B. +49 für Deutschland. Passwort für das Dexcom Share Konto. Dexcom Share einrichten - Um Daten von Dexcom Share zu empfangen, müssen folgende Schritte erfüllt sein:\n- in der Dexcom App, welche mit dem Sensor verbunden ist, Teilen aktivieren\n- einmalig die Einladung in der Dexcom Follower App annehmen (die App kann danach wieder deinstalliert werden)\n\nWichtig: es funktioniert nicht mit einem Follower Benutzer! Fehlende Berechtigung! Für die gesetzt Einstellung \'%1$s\' wurde die Berechtigung entfernt!\nBitte \"OK\" drücken, um zu der entsprechenden Berechtigungseinstellung weitergeleitet zu werden, um diese zu wieder zu aktivieren.\nBeim Drücken von \"Abbrechen\" wird die Einstellung deaktiviert und kann später über das Menü wieder aktiviert werden. Broadcast Service API @@ -551,7 +548,7 @@ Tage für Ruhezeit Legen Sie fest, an welchen Wochentagen die Ruhezeit zur gewählten Startzeit aktiviert werden soll. - Die App ist aufgrund des farbigen Layouts in den Benachrichtigungen abegstürzt!\nDie Einstellung wurde daher deaktiviert.\nDas Problem tritt bisher nur bei Samsung auf und scheint ein Problem auf Seite von Samsung zu sein.\nBitte senden Sie daher einen Fehlerbericht an Samsung. Vielen Dank! + Die App ist aufgrund des farbigen Layouts in den Benachrichtigungen abegstürzt!\nDie Einstellung wurde daher deaktiviert.\nEin Neustart des Telefons behebt das normalerweise. Benachrichtigung automatisch schließen Alarm Benachrichtigung automatisch schließen, nachdem der Ton und die Vibration gestoppt haben, frühestens aber nach 30 Sekunden.\nWenn \"Erneut auslösen\" aktiv ist, wird die Benachrichtigung erst geschlossen, wenn der Alarm nicht mehr erneut ausgelöst wird. Einstellungen erfolgreich gespeichert. @@ -567,7 +564,6 @@ Logs vom Telefon in eine Datei speichern. Logs von einer verbundenen Wear OS Uhr in eine Datei speichern. Wiederholung - "Ton und Vibration wiederholen.\n0: Keine Wiederholung.\nPositive Werte: Wiederholung in Minuten. \n-1: Wiederholung bis der Alarm geschlossen wird. " Keine Vibration Vibration Modus @@ -595,6 +591,8 @@ Interne Nachrichten von dieser App werden verarbeitet. Interne Nachrichten von dieser App werden ignoriert. Temporär deaktiviert + Temporär deaktiviert bis + Schlummern bis Trend @@ -639,4 +637,75 @@ Alarmsymbol aktivieren Alarmsymbol auf dem Hauptbildschirm aktivieren, um den Alarmstatus umzuschalten. + + + Fehler, bitte melde dich erneut in der LibreLinkUp App an und prüfe, ob Schritte erforderlich sind! + Fehlertyp %1$s, bitte melde dich erneut in der LibreLinkUp App an und prüfe, ob Schritte erforderlich sind! + Neue Nutzungsbedingungen automatisch akzeptieren + Wenn du dies aktivierst, akzeptierst du automatisch neue Nutzungsbedingungen, um einen kontinuierlichen Datenempfang zu gewährleisten. Du wirst nicht separat über neue Bedingungen informiert. Wenn du sie lesen möchtest, deaktiviere diese Einstellung und wende dich im Falle von Fehler 4 an die LibreLinkUp App. + Neue Nutzungsbedingungen müssen akzeptiert werden. Aktiviere die automatische Annahme in den LibreLinkUp-Quelleinstellungen oder melde dich erneut bei der LibreLinkUp App an und akzeptiere die Bedingungen. + GlucoDataHandler kann neue Nutzungsbedingungen für LibreLinkUp automatisch akzeptieren.\nDrücke OK, um diese Funktion zu aktivieren, oder Abbrechen, um die Bedingungen in der LibreLinkUp App zu lesen und manuell zu akzeptieren. + Falscher Benutzername oder Passwort. Bitte überprüfe deine LibreLinkUp-Quelleinstellungen. Versuche gegebenenfalls, dich erneut anzumelden oder die LibreLinkUp App neu zu installieren und deine Kontoeinstellungen zu überprüfen.\nBitte beende alle anderen Apps, die auf dasselbe Konto zugreifen! + Letzter Glukosewert auf dem Server von %1$s + Keine Glukosewerte auf dem Server gefunden. + Zu viele Anfragen! + Bitte beende alle anderen Apps, die auf dasselbe Konto zugreifen! + Verbindungsaufbau + Akkustand senden + Sende den Akkustand an die Uhr, um ihn in der Komplikation und auf dem Hauptbildschirm anzuzeigen. + Akkustand anzeigen + Lege die minimale Zeit (in Minuten) zwischen Glukosemessungen fest, bevor ein neues Update an die Uhr gesendet wird.\nAlarme und sehr tiefe Werte werden immer gesendet. + Lege die minimale Zeit (in Minuten) zwischen Glukosemessungen fest, bevor ein neues Update an die Empfänger gesendet wird. + Der Alarm ertönt und vibriert wiederholt, bis die Benachrichtigung geschlossen oder die eingestellte maximale Alarmdauer erreicht ist. + Maximale Alarmdauer + Gib die maximale Zeit (in Minuten) an, die der Alarm ertönt und vibriert. Ein Wert von 0 bedeutet, dass der Alarm unbegrenzt wiederholt wird. + Um Quellen zu aktivieren, richte sie bitte zuerst in der Telefon-App ein. Aktiviere Quellen auf deiner Uhr nur, wenn du die Daten noch nicht auf deinem Telefon empfängst. + Hinweis des Entwicklers + Diese App wurde in meiner Freizeit als Hobbyprojekt entwickelt. Daher kann ich nicht garantieren, dass alle Funktionen auf jedem Gerät einwandfrei funktionieren. Ich arbeite jedoch stetig an Verbesserungen.\nBei Problemen oder Fragen kontaktieren Sie mich bitte direkt, bevor Sie eine negative Bewertung abgeben.\nUm über wichtige Updates und Fehlerbehebungen informiert zu werden, können Sie sich gerne in der Facebook-Gruppe oder bei Google Groups anmelden. + + Um Daten von Dexcom Share-Servern zu empfangen:\nIn der Dexcom App:\n - erstelle einen Follower\n - überprüfe unter Verbindungen, ob \"Share Ein\" steht + In GlucoDataHandler:\n - Benutzername und Passwort des Dexcom Clarity-Kontos eingeben \n (bei Verwendung der Telefonnummer, Länderkennung mit angeben!)\n - überprüfe die Einstellung für US Konto\nWichtig: Es funktioniert nicht mit einem Follower-Benutzer! + + Aktualisiert + Alarme + + Zifferblätter + Informationen zu Zifferblättern von Drittanbietern für Wear OS. + Einrichtung + Zifferblätter von Drittanbietern + Diese App bietet kein eigenes Zifferblatt, sondern mehrere Komplikationen (die kleinen runden Elemente auf dem Zifferblatt), die für Zifferblätter verwendet werden können, welche Komplikationen von Drittanbietern unterstützen. + Standard + Die meisten Hersteller von Smartwatches bieten bereits Zifferblätter an, die mit den Komplikationen von GlucoDataHandler verwendet werden können.\nBeispielsweise sind die meisten Samsung \"Info\"-Zifferblätter kompatibel. + Pujie + Pujie ist eine App zum Erstellen von Zifferblättern mit einer Cloud-Bibliothek.\nUm kompatible Zifferblätter zu finden, suchen Sie in der Bibliothek nach Zifferblättern, die mit \"GDH_\" beginnen.\nHinweis: Pujie ist nicht mit Wear OS 5-Geräten kompatibel.\nTippen Sie hier, um die App im Play Store zu öffnen. + Diabetic Masked Man + Diabetic Masked Man erstellt Zifferblätter von Drittanbietern, die speziell für die Verwendung mit GlucoDataHandler entwickelt wurden.\nTippen Sie hier, um seine Play Store-Seite zu öffnen. + GDC Zifferblätter + GDC Zifferblätter wurden für die Verwendung mit GlucoDataHandler entwickelt.\nTippen Sie hier, um die Play Store-Seite zu öffnen. + Komplikationen immer aktualisieren + Neue Zuckerwerte immer an die Uhr senden.\nDeaktiviere diese Einstellung, um keine Daten an die Uhr zu senden, solange der Bildschirm der Uhr ausgeschaltet ist, um Akku zu sparen.\nAlarmwerte werden immer an die Uhr gesendet.\nWICHTIG: Der veraltete Alarm auf der Uhr wird deaktiviert, wenn diese Einstellung deaktiviert ist! + Sehr tiefen Alarm erzwingen + Den sehr tiefen Alarm auch dann benachrichtigen, wenn die Schlummerfunktion oder die Ruhezeiten für alle Alarme aktiv sind. + + Wenn dieser Fehler weiterhin besteht, versuchen Sie die Libre App neu zu installieren. Dies behebt das Problem normalerweise. + + Server + Standard (.io) + Russisch (.ru) + Name + Name des Benutzers/Patienten, der in der Benachrichtigung angezeigt und beim Vorlesen angesagt werden soll.\nDies sollte verwendet werden, wenn mehrere Varianten von GlucoDataAuto verwendet werden, andernfalls kann es leer bleiben. + + Video-Anleitung + Klicken Sie hier, um zum YouTube-Video zu gelangen, das die Einrichtung dieser Quelle veranschaulicht. Vielen Dank an Diabetic Masked Man für die Erstellung der Videos. + + 30 Minuten + 60 Minuten + 90 Minuten + 120 Minuten + Schlummern + Wähle bis zu 3 Zeiten für Schlummern aus, die als Schaltflächen in der Alarmbenachrichtigung angezeigt werden sollen. + + IOB/COB-Werte anzeigen + IOB- und COB-Werte anzeigen, falls verfügbar. + diff --git a/common/src/main/res/values-es/strings.xml b/common/src/main/res/values-es/strings.xml index bd0be1cd4..25e2fa9c2 100644 --- a/common/src/main/res/values-es/strings.xml +++ b/common/src/main/res/values-es/strings.xml @@ -310,7 +310,7 @@ - habilita Transmisión compatible\n- verifica que \"Identificar receptor\" esté vacío o, si ya existe una entrada, agrega \"de.michelinside.glucodatahandler\" separado por un espacio - habilita "Transmisión compatible"\n- verifica que "Identificar receptores" esté vacío o agrega una nueva línea con "de.michelinside.glucodataauto" Configurar LibreLinkUp - IMPORTANTE: esto no es la cuenta de LibreView!\nPara activar LibreLinkUp:\n- abre tu aplicación FreeStyle Libre y selecciona en el menú Compartir o Aplicaciones conectadas\n- activa la conexión de LibreLinkUp\n- instala LibreLinkUp desde PlayStore\n- configura tu cuenta y espera la invitación + IMPORTANTE: esto no es la cuenta de LibreView!\nPara activar LibreLinkUp:\n- abre tu aplicación FreeStyle Libre y selecciona en el menú Compartir o Aplicaciones conectadas\n- activa la conexión de LibreLinkUp\n- instala LibreLinkUp desde Play Store\n- configura tu cuenta y espera la invitación Contacto Soporte de IOB/COB Recibe también valores de IOB y COB del punto de conexión pebble de nightscout. @@ -386,8 +386,6 @@ Categorías de alarma Notificación a pantalla completa Habilita la notificación a pantalla completa en la pantalla de bloqueo.\nEsto puede requerir un permiso adicional. - Snooze en notificación - Si está habilitado, las notificaciones también incluirán botones para activar la posposición. Descartar protección de pantalla Por favor, habilita esta configuración si tienes problemas con la notificación a pantalla completa.\nEn algunos dispositivos, esto causará la pantalla de desbloqueo mientras se activa la notificación a pantalla completa.\nPor lo tanto, no la habilites si no tienes ningún problema. Forzar sonido de alarma @@ -464,7 +462,7 @@ Transferir valores Visualización Relojes BangleJS - Para usar GlucoDataHandler en relojes con Wear OS, por favor instálalo desde Google Play Store.\nEsta aplicación no proporciona una esfera de reloj, sino que proporciona varias complicaciones (las pequeñas partes redondas de la esfera del reloj), que se pueden usar en esferas de reloj compatibles con complicaciones de terceros. + Para usar GlucoDataHandler en relojes con Wear OS, por favor instálalo desde Google Play Store. Verificar conexión Presiona aquí para volver a verificar la conexión con Wear OS.\nTambién puedes activar esta verificación al presionar sobre la información de la conexión en la pantalla de inicio (incluso en la aplicación de Wear OS). WatchDrip+ admite varios dispositivos MiBand y Amazfit.\nPara obtener una lista completa de dispositivos compatibles y más información sobre cómo configurar WatchDrip+, por favor presiona aquí. @@ -521,7 +519,6 @@ Nombre de usuario, correo electrónico o número de teléfono para iniciar sesión en la cuenta de Dexcom Share.\nIMPORTANTE: el número de teléfono debe incluir el código de país, por ejemplo, +34 para España.\n Contraseña para la cuenta de Dexcom Share. Configurar Dexcom Share - Para recibir datos de los servidores de Dexcom Share, necesitas tener:\n- compartir habilitado en la aplicación Dexcom que está conectada al sensor\n- aceptada la invitación en la aplicación Dexcom Follower (puedes desinstalarla después)\n\nImportante: ¡no funciona con un usuario seguidor! Missing permission! ¡La configuración \'%1$s\' está activada, pero el permiso relacionado no está concedido! \nPresiona \"Aceptar\" para ser dirigido al cuadro de diálogo de permisos o presiona \"Cancelar\" para desactivar esta configuración.\n Broadcast Service API @@ -561,7 +558,7 @@ Selecciona la hora a la que quieres que terminen tus horas de silencio. Días para horas de silencio Especifica los días de la semana en los que las horas de silencio deben comenzar a la hora de inicio seleccionada. - La aplicación se bloquea debido al diseño de notificación en color!\nSe ha desactivado para evitar este bloqueo.\nEste es un problema en los teléfonos Samsung, así que cree un informe de error en la aplicación Samsung Members. ¡Gracias! + La aplicación se bloquea debido al diseño de notificación en color!\nSe ha desactivado para evitar este bloqueo.\nUn reinicio del teléfono suele solucionar esto. Cerrar notificación automáticamente Cerrar automáticamente la notificación de alarma después de que el sonido y la vibración hayan finalizado, pero solo después de al menos 30 segundos.\nSi la reactivación está activa, la notificación se cerrará si no hay ningún otro desencadenante. Configuración guardada correctamente @@ -578,7 +575,6 @@ Guardar registros del teléfono en un archivo. Guardar registros del reloj Wear OS conectado en un archivo. Repetir - Repetir el sonido y la vibración de la alarma. \n0: Desactiva la función de repetición. \nValores positivos: Establece el intervalo de repetición en minutos. \n-1: Activa la repetición ilimitada. Sin vibración Modo de vibración Aumento rápido @@ -604,6 +600,8 @@ Se gestionarán los mensajes internos de esta fuente. Se ignorarán los mensajes internos de esta fuente. Desactivado temporalmente + Desactivadas temporalmente hasta + Pospuestas hasta Tendencia @@ -644,4 +642,76 @@ Pronunciar está desactivado Habilitar icono de alarma Habilitar icono de alarma en la pantalla principal para cambiar el estado de la alarma. + + + + Error, por favor vuelve a iniciar sesión en la aplicación LibreLinkUp y comprueba si hay algún paso que debas realizar. + Tipo de error %1$s, por favor vuelve a iniciar sesión en la aplicación LibreLinkUp y comprueba si hay algún paso que debas realizar. + Aceptar automáticamente los nuevos términos + Al habilitar esto, aceptas automáticamente los nuevos Términos de uso para garantizar la recepción continua de datos. No se te informará sobre los nuevos términos por separado. Si deseas leerlos, deshabilita esta configuración y consulta la aplicación LibreLinkUp en caso de error 4. + Se deben aceptar los nuevos Términos de uso. Habilita la aceptación automática en la configuración de origen de LibreLinkUp o vuelve a iniciar sesión en la aplicación LibreLinkUp y acepta los términos. + GlucoDataHandler puede aceptar automáticamente los nuevos Términos de uso para LibreLinkUp.\nPulsa Aceptar para habilitar esta función o Cancelar para leer y aceptar manualmente los términos en la aplicación LibreLinkUp. + Nombre de usuario o contraseña incorrectos. Por favor, verifica la configuración de origen de LibreLinkUp. Si es necesario, intenta iniciar sesión de nuevo o reinstalar la aplicación LibreLinkUp y comprobar la configuración de tu cuenta.\nPor favor, detén todas las demás aplicaciones que acceden a la misma cuenta. + Última lectura de glucosa en el servidor de %1$s + No se encontraron lecturas de glucosa en el servidor. + ¡Demasiadas solicitudes! + Por favor, detén todas las demás aplicaciones que acceden a la misma cuenta. + Esperando datos + Enviar nivel de batería + Enviar el nivel de batería al reloj para mostrarlo en la complicación y en la pantalla principal. + Mostrar nivel de batería + Establece el tiempo mínimo (en minutos) entre las mediciones de glucosa antes de enviar una nueva actualización al reloj.\nLas alarmas y las mediciones muy bajas siempre se enviarán. + Establece el tiempo mínimo (en minutos) entre las mediciones de glucosa antes de enviar una nueva actualización a los receptores. + La alarma sonará y vibrará repetidamente hasta que descartes la notificación o se alcance la duración máxima de la alarma establecida. + Duración máxima de la alarma + Especifica el tiempo máximo (en minutos) que la alarma sonará y vibrará. Un valor de 0 significa que la alarma se repetirá indefinidamente. + Para habilitar las fuentes, configúralas primero en la aplicación del teléfono. Activa las fuentes en tu reloj solo si aún no recibes los datos en tu teléfono. + Nota del desarrollador + Esta aplicación fue desarrollada en mi tiempo libre como un proyecto de afición. Por lo tanto, no puedo garantizar que todas las funciones funcionen perfectamente en todos los dispositivos. Sin embargo, estoy trabajando constantemente en mejoras.\nSi encuentras algún problema o tienes alguna pregunta, por favor contáctame directamente antes de dejar una reseña negativa. \nPara mantenerte informado sobre actualizaciones importantes y correcciones de errores, puedes unirte al grupo de Facebook o a los Grupos de Google. + + Para recibir datos de los servidores Dexcom Share:\nEn la aplicación Dexcom:\n - Crea un seguidor\n - En \"Conexiones\", asegúrate de que se muestre \"Compartir\" + En GlucoDataHandler:\n - Introduce tu nombre de usuario y contraseña de Dexcom Clarity\n (si utilizas tu número de teléfono como nombre de usuario, incluye tu código de país)\n - Comprueba la configuración para una cuenta de EE. UU.\nImportante: ¡Esto no funciona con una cuenta de seguidor! + + Actualizado + Alarmas + + Esferas de reloj + Información sobre watchfaces de terceros para Wear OS. + Configuración + Esferas de reloj de terceros + Esta aplicación no proporciona una watchface, sino que proporciona varias complicaciones (las pequeñas partes redondas de la watchface), que se pueden utilizar para watchfaces que admiten complicaciones de terceros. + Estándar + La mayoría de los fabricantes de relojes ya proporcionan watchfaces que se pueden utilizar con la complicación GlucoDataHandler.\nPor ejemplo, la mayoría de las watchfaces \"Info\" de Samsung son compatibles. + Pujie + Pujie es una aplicación de creación de watchfaces con una biblioteca en la nube.\nPara encontrar watchfaces compatibles, busca en la biblioteca watchfaces que comiencen con \"GDH_\".\nNota: Pujie no es compatible con dispositivos Wear OS 5.\nPulsa aquí para abrir la aplicación en Play Store. + Diabetic Masked Man + Diabetic Masked Man crea watchfaces de terceros diseñadas específicamente para su uso con GlucoDataHandler.\nPulsa aquí para abrir su página de Play Store. + GDC Watch Faces + GDC Watch Faces están diseñadas para su uso con GlucoDataHandler.\nPulsa aquí para abrir la página de Play Store. + Actualizar el reloj siempre + Enviar siempre nuevos valores de glucosa al reloj.\nDesactiva esto para evitar actualizaciones de datos en el reloj mientras la pantalla del reloj está apagada para ahorrar batería.\nLos valores de alarma siempre se enviarán al reloj.\nIMPORTANTE: ¡La alarma obsoleta se desactiva en el reloj cuando esta configuración está desactivada! + Alertar siempre para valores muy bajos + Esta alarma te notificará incluso si la función de repetición o el horario de silencio general para todas las alarmas están activos, lo que garantiza que se te avise de los niveles de glucosa muy bajos. + + Si este error persiste, intenta reinstalar la aplicación LibreLink. Esto suele resolver el problema. + Servidor + Predeterminado (.io) + Ruso (.ru) + + Nombre + Nombre del usuario/paciente, que debe mostrarse en la notificación y durante el habla.\nEsto debe usarse si está utilizando múltiples variantes de GlucoDataAuto, de lo contrario, podría estar vacío. + + Videotutorial + Haga clic aquí para ir al video de YouTube que ilustra la configuración de esta fuente. Muchas gracias a Diabetic Masked Man por crear los videos. + + 30 minutos + 60 minutos + 90 minutos + 120 minutos + Snooze en notificación + Elija hasta 3 duraciones de repetición para mostrar como botones en la notificación de alarma. + + Mostrar valores de IOB/COB + Mostrar valores de IOB y COB, si están disponibles. + \ No newline at end of file diff --git a/common/src/main/res/values-fr/strings.xml b/common/src/main/res/values-fr/strings.xml index e5e3ad0d2..ced8c06e5 100644 --- a/common/src/main/res/values-fr/strings.xml +++ b/common/src/main/res/values-fr/strings.xml @@ -307,7 +307,7 @@ - activez "Diffusion compatible" - vérifiez que "Identifier le récepteur" est vide ou si une entrée existe déjà, ajoutez "de.michelinside.glucodatahandler" séparé par un espace - activez "Diffusion compatible"\n- vérifiez que "Identifier les récepteurs" est vide ou ajoutez une nouvelle ligne avec "de.michelinside.glucodataauto" Configurer LibreLinkUp - IMPORTANT: ce n\'est pas le compte utilisé pour l\'application FreeStyle Libre!\nPour activer le suiveur LibreLinkUp:\n- ouvrez votre application FreeStyle Libre et sélectionnez dans le menu Partager ou Applications connectées\n- activez la connexion LibreLinkUp\n- installez LibreLinkUp depuis le PlayStore\n- configurez votre compte et attendez l\'invitation + IMPORTANT: ce n\'est pas le compte utilisé pour l\'application FreeStyle Libre!\nPour activer le suiveur LibreLinkUp:\n- ouvrez votre application FreeStyle Libre et sélectionnez dans le menu Partager ou Applications connectées\n- activez la connexion LibreLinkUp\n- installez LibreLinkUp depuis le Play Store\n- configurez votre compte et attendez l\'invitation Contact Prise en charge IOB/COB Recevez également les valeurs IOB et COB du point de terminaison Nightscout Pebble. @@ -385,8 +385,6 @@ Catégories d\'alarmes Notification plein écran Active la notification plein écran sur l\'écran de verrouillage.\nCela peut nécessiter une autorisation supplémentaire. - Snoozer à partir de la notification - Si cette option est activée, les notifications incluront également des boutons pour activer la répétition. Rejeter le verrouillage du clavier Veuillez activer ce paramètre si vous rencontrez des problèmes avec la notification plein écran.\nSur certains appareils, cela provoquera le déverrouillage de l\'écran lorsque la notification plein écran est déclenchée.\nVeuillez donc ne pas l\'activer si vous n\'avez aucun problème. Forcer le son de l\'alarme @@ -463,7 +461,7 @@ Transférer les valeurs Visualisation Montres BangleJS - Pour utiliser GlucoDataHandler sur les montres Wear OS, veuillez l\'installer depuis le Google Playstore.\nCette application ne fournit pas de cadran de montre, mais fournit plusieurs complications (les petites parties rondes du cadran de la montre), qui peuvent être utilisées pour les cadrans de montre prenant en charge les complications tierces. + Pour utiliser GlucoDataHandler sur les montres Wear OS, veuillez l\'installer depuis le Google Play Store. Vérifier la connexion Appuyez ici pour revérifier la connexion à Wear OS.\nVous pouvez également déclencher cette vérification en appuyant sur les informations de connexion sur l\'écran d\'accueil (même dans l\'application Wear OS). WatchDrip+ prend en charge plusieurs appareils MiBand et Amazfit.\nPour obtenir une liste complète des appareils pris en charge et plus d\'informations sur la configuration de WatchDrip+, veuillez appuyer ici. @@ -520,7 +518,6 @@ Nom d\'utilisateur ou e-mail pour le compte Dexcom Share. Mot de passe pour le compte Dexcom Share. Configurer Dexcom Share - Pour recevoir des données des serveurs Dexcom Share, vous devez avoir:\n- le partage activé sur l\'application Dexcom connectée au capteur\n- l\'invitation acceptée sur l\'application Dexcom Follower (vous pouvez la désinstaller par la suite)\n\nImportant: cela ne fonctionne pas avec un utilisateur suiveur! Autorisation manquante! Le paramètre \'%1$s\' est défini, mais l\'autorisation correspondante n\'est pas accordée! Appuyez sur "OK" pour être redirigé vers la boîte de dialogue d\'autorisation ou appuyez sur "Annuler" pour désactiver ce paramètre. API du service de diffusion @@ -561,7 +558,7 @@ Sélectionnez l\'heure à laquelle vous souhaitez que vos heures calmes se terminent. Jours pour les heures calmes Spécifiez les jours de la semaine où les heures calmes doivent commencer à l\'heure de début sélectionnée. - L\'application se bloque à cause de la mise en page des notifications colorées !\nElle a été désactivée pour éviter ce plantage.\nIl s\'agit d\'un problème sur les téléphones Samsung, veuillez donc créer un rapport d\'erreur dans l\'application Samsung Members. Merci! + L\'application se bloque à cause de la mise en page des notifications colorées !\nElle a été désactivée pour éviter ce plantage.\nUn redémarrage du téléphone corrige généralement ce problème. Fermer automatiquement la notification Fermer automatiquement la notification d\'alarme une fois que le son et les vibrations ont cessé, mais seulement après au moins 30 secondes.\nSi le redéclenchement est actif, la notification sera fermée s\'il n\'y a pas de déclencheur supplémentaire. Paramètres enregistrés avec succès @@ -578,7 +575,6 @@ Enregistrer les journaux du téléphone dans un fichier. Enregistrer les journaux de la montre Wear OS connectée dans un fichier. Répéter - Répéter le son et les vibrations de l\'alarme. \n0 : Désactive la fonctionnalité de répétition. \nValeurs positives : Définir l\'intervalle de répétition en minutes. \n-1 : Active la répétition illimitée. Pas de vibration Mode de vibration Augmentation rapide @@ -604,6 +600,8 @@ Les messages internes de cette source seront traités. Les messages internes de cette source seront ignorés. Désactivé temporairement + Reportées jusqu\'à + Désactivées temporairement jusqu\'à Tendance @@ -646,4 +644,75 @@ Activer l\'icône d\'alarme Activer l\'icône d\'alarme sur l\'écran principal pour activer/désactiver l\'état de l\'alarme. + + + Erreur, veuillez vous reconnecter à l\'application LibreLinkUp et vérifier les étapes à suivre ! + Type d\'erreur %1$s, veuillez vous reconnecter à l\'application LibreLinkUp et vérifier les étapes à suivre ! + Accepter automatiquement les nouvelles conditions d\'utilisation + En activant cette option, vous acceptez automatiquement les nouvelles conditions d\'utilisation pour assurer une réception continue des données. Vous ne serez pas informé séparément des nouvelles conditions. Si vous souhaitez les lire, désactivez ce paramètre et reportez-vous à l\'application LibreLinkUp en cas d\'erreur 4. + Les nouvelles conditions d\'utilisation doivent être acceptées. Activez l\'acceptation automatique dans les paramètres de source LibreLinkUp, ou reconnectez-vous à l\'application LibreLinkUp et acceptez les conditions. + GlucoDataHandler peut accepter automatiquement les nouvelles conditions d\'utilisation de LibreLinkUp.\nAppuyez sur OK pour activer cette fonctionnalité, ou sur Annuler pour lire et accepter manuellement les conditions dans l\'application LibreLinkUp. + Nom d\'utilisateur ou mot de passe incorrect. Veuillez vérifier vos paramètres de source LibreLinkUp. Si nécessaire, essayez de vous reconnecter ou de réinstaller l\'application LibreLinkUp et de vérifier les paramètres de votre compte.\nVeuillez arrêter toutes les autres applications qui accèdent au même compte! + Dernière lecture de glucose sur le serveur à partir de %1$s + Aucune lecture de glucose trouvée sur le serveur. + Trop de requêtes! + Veuillez arrêter toutes les autres applications qui accèdent au même compte! + En attente de données + Envoyer le niveau de batterie + Envoyer le niveau de batterie à la montre pour l\'afficher dans la complication et sur l\'écran principal. + Afficher le niveau de batterie + Définissez le temps minimum (en minutes) entre les mesures de glucose avant d\'envoyer une nouvelle mise à jour à la montre.\nLes alarmes et les mesures très basses seront toujours envoyées. + Définissez le temps minimum (en minutes) entre les mesures de glucose avant d\'envoyer une nouvelle mise à jour aux récepteurs. + L\'alarme sonnera et vibrera de manière répétée jusqu\'à ce que vous rejetiez la notification ou que la durée maximale de l\'alarme définie soit atteinte. + Durée maximale de l\'alarme + Spécifiez la durée maximale (en minutes) pendant laquelle l\'alarme sonnera et vibrera. Une valeur de 0 signifie que l\'alarme se répétera indéfiniment. + Pour activer les sources, veuillez les configurer d\'abord dans l\'application du téléphone. Activez les sources sur votre montre uniquement si vous ne recevez pas encore les données sur votre téléphone. + Note du développeur + Cette application a été développée pendant mon temps libre en tant que projet de loisir. Par conséquent, je ne peux pas garantir que toutes les fonctionnalités fonctionneront parfaitement sur tous les appareils. Cependant, je travaille constamment à des améliorations.\nSi vous rencontrez des problèmes ou avez des questions, veuillez me contacter directement avant de laisser un avis négatif. \nPour rester informé des mises à jour importantes et des corrections de bugs, vous pouvez rejoindre le groupe Facebook ou les Groupes Google. + + Pour recevoir des données des serveurs Dexcom Share :\nDans l’application Dexcom :\n - Créez un abonné\n - Sous « Connexions », assurez-vous que « Partager » est affiché + Dans GlucoDataHandler :\n - Saisissez votre nom d’utilisateur et votre mot de passe Dexcom Clarity\n (si vous utilisez votre numéro de téléphone comme nom d’utilisateur, veuillez inclure votre indicatif de pays)\n - Vérifiez le paramètre pour un compte américain\nImportant : cela ne fonctionne pas avec un compte d’abonné ! + + Mis à jour + Alarmes + + Cadrans de montre + Informations sur les cadrans de montre tiers pour Wear OS. + Configuration + Cadrans de montre tiers + Cette application ne fournit pas de cadran de montre, mais plusieurs complications (les petites parties rondes du cadran de la montre), qui peuvent être utilisées pour les cadrans de montre prenant en charge les complications tierces. + Standard + La plupart des fabricants de montres proposent déjà des cadrans de montre qui peuvent être utilisés avec la complication GlucoDataHandler.\nPar exemple, la plupart des cadrans de montre « Info » de Samsung sont compatibles. + Pujie + Pujie est une application de création de cadrans de montre avec une bibliothèque cloud.\nPour trouver des cadrans de montre compatibles, recherchez dans la bibliothèque les cadrans de montre commençant par « GDH_ ».\nRemarque : Pujie n’est pas compatible avec les appareils Wear OS 5.\nAppuyez ici pour ouvrir l’application dans le Play Store. + Diabetic Masked Man + Diabetic Masked Man crée des cadrans de montre tiers spécialement conçus pour être utilisés avec GlucoDataHandler.\nAppuyez ici pour ouvrir sa page Play Store. + Cadrans de montre GDC + Les cadrans de montre GDC sont conçus pour être utilisés avec GlucoDataHandler.\nAppuyez ici pour ouvrir la page Play Store. + Toujours mettre à jour les complications + Toujours envoyer les nouvelles valeurs de glucose à la montre.\nDésactivez cette option pour empêcher les mises à jour de données sur la montre lorsque l\'écran de la montre est éteint afin d\'économiser la batterie.\nLes valeurs d\'alarme seront toujours envoyées à la montre.\nIMPORTANT : l\'alarme obsolète est désactivée sur la montre lorsque ce paramètre est désactivé ! + Toujours alerter pour les valeurs très basses + Cette alarme vous avertira même si la fonction de répétition ou les heures de silence générales pour toutes les alarmes sont activées, vous assurant ainsi d\'être alerté des niveaux de glucose très bas. + + Si cette erreur persiste, essayez de réinstaller l\'application LibreLink. Cela résout souvent le problème. + Serveur + Par défaut (.io) + Russe (.ru) + + Nom + Nom de l\'utilisateur/patient, qui doit être affiché dans la notification et pendant la parole.\nCeci doit être utilisé si vous utilisez plusieurs variantes de GlucoDataAuto, sinon il pourrait être vide. + + Tutoriel vidéo + Cliquez ici pour accéder à la vidéo YouTube qui illustre la configuration de cette source. Un grand merci à Diabetic Masked Man pour la création des vidéos. + + 30 minutes + 60 minutes + 90 minutes + 120 minutes + Snoozer à partir de la notification + Choisissez jusqu\'à 3 durées de répétition à afficher sous forme de boutons sur la notification d\'alarme. + + Afficher les valeurs IOB/COB + Afficher les valeurs IOB et COB, si disponibles. + \ No newline at end of file diff --git a/common/src/main/res/values-it/strings.xml b/common/src/main/res/values-it/strings.xml new file mode 100644 index 000000000..b70194376 --- /dev/null +++ b/common/src/main/res/values-it/strings.xml @@ -0,0 +1,707 @@ + + + it + + Sensore numero seriale + Valore + Raw + Delta + Tasso + Timestamp + Orario IOB/COB + Differenza di tempo + Allarme + Sorgente + al minuto + Nessun dato ricevuto ancora!\nSi prega di configurare prima una fonte. + + %1$d min + \> 1h + + in rapida salita + in salita + in leggera salita + stabile + in leggera discesa + in discesa + in rapida discesa + + OK + Annulla + + - + molto basso + basso + alto + molto alto + + L\'ottimizzazione della batteria è abilitata. + Fonte: non attiva + Wear: connesso (%1$s) + Wear: disconnesso + Android Auto: connesso + Android Auto: disconnesso + L\'accessibilità ad alto contrasto è attiva!\nPremi qui per andare alle impostazioni. + Nessun nuovo valore da %1$d minuti! + + Impostazioni + Sorgenti + GitHub + Aggiornamenti + Segnala un problema + Guida + + + + Nuovo valore della glicemia + Valore obsoleto + Allarme glicemia + Stato della connessione Wear + Stato della connessione Android Auto + Modifica dell\'intervallo di destinazione della glicemia + + + + Telefono + Wear + + Non attivo + In corso + OK + Nessun nuovo valore + Nessuna connessione Internet + Errore + + Intervallo + Intervallo di richiesta dati dalla sorgente selezionata. + Ritardo + Ritardo in secondi prima di richiedere i dati dal cloud, perché ci vuole del tempo prima che i dati siano presenti lì. + + LibreLinkUp + Abilita + Abilita il follower LibreLinkUp, se sono impostati utente e password. + E-mail + E-mail account LibreLinkUp + Password + Password account LibreLinkUp + Riconnetti + Al prossimo intervallo verrà eseguito un nuovo accesso. + + Nightscout + Abilita + Abilita il follower Nightscout. + URL di Nightscout + URL del server Nightscout. + API_SECRET + API_SECRET per accedere ai dati (facoltativo). + Token di accesso + Aggiungi il tuo token di accesso, se ne hai bisogno (facoltativo). + + Sempre + 1 minuto + 5 minuti + 10 minuti + 15 minuti + 20 minuti + 30 minuti + Solo allarme + + + disabilitato + non connesso + connesso + errore: %s + + + + + Generale + Notifiche + Widget + Android Auto + Inoltra valori della glicemia + Intervallo di destinazione + Colori + + Valori fittizi + Glicemia molto alta + Limite per valori di glicemia molto alti. + Glicemia alta + Limite per valori di glicemia alti. + Glicemia target massima + Valore massimo per l\'intervallo target. + Glicemia target minima + Valore minimo per l\'intervallo target. + Glicemia molto bassa + Limite per valori di glicemia molto bassi. + Glicemia bassa + Limite per valori di glicemia bassi. + Notifiche Android Auto + Notifica per Android Auto. + Intervallo di notifica + Intervallo minimo per la visualizzazione delle notifiche per un nuovo valore.\nGli allarmi mostreranno sempre una notifica. + Solo allarme + Attiverà le notifiche solo per gli allarmi nel loro intervallo specifico.\nI valori molto bassi mostreranno sempre una notifica. + Lettore multimediale fittizio + Mostra il valore della glicemia come brano corrente per Android Auto. + Invia trasmissione glucodata + Invia trasmissione glucodata per i nuovi valori ricevuti da qualsiasi fonte. + Identifica i destinatari dei glucodata + Scegli i destinatari a cui inviare la trasmissione glucodata. Una trasmissione globale verrà inviata a ogni app che si registra dinamicamente sulla trasmissione. + Invia trasmissione libre patchata + Inoltra i valori della glicemia a xDrip+. Seleziona \'Libre (app patchata)\' come fonte in xDrip+. + Identifica i destinatari della trasmissione libre patchata + Scegli i destinatari a cui inviare la trasmissione libre patchata. Una trasmissione globale verrà inviata a ogni app che si registra dinamicamente sulla trasmissione. + Invia trasmissione xDrip+ + Invia trasmissione come fa xDrip+. Ad esempio ad AAPS. + Identifica i destinatari della trasmissione xDrip+ + Scegli i destinatari a cui inviare la trasmissione xDrip+. Una trasmissione globale verrà inviata a ogni app che si registra dinamicamente sulla trasmissione. + Utilizza mmol/l + Utilizza mmol/l invece di mg/dl. + Delta di 5 minuti + Attiva questa opzione per utilizzare i valori delta per un intervallo di 5 minuti, altrimenti viene utilizzato un intervallo di 1 minuto. + Colore molto alto/basso + Colore per valori di glicemia molto alti e molto bassi. + Colore alto/basso + Colore per valori di glicemia alti e bassi. + Colore target + Colore per valori di glicemia nell\'intervallo target. + Notifica + Mostra una notifica permanente con la freccia di tendenza corrente, il valore della glicemia e il valore delta nella barra di stato.\nQuesta notifica impedisce ad Android di arrestare questa app. + Icona della barra di stato + Stile della piccola icona della barra di stato della notifica. + Icona grande della barra di stato + Icona più grande nella barra di stato. Disabilita questa impostazione se l\'icona è troppo grande e viene tagliata. + Nascondi il contenuto + Questo rimuoverà il contenuto della notifica, per avere solo una piccola notifica con l\'icona correlata. + 2. notifica + Mostra una notifica senza contenuto per avere un\'icona aggiuntiva nella barra di stato. + 2. icona della barra di stato + Stile della piccola icona della barra di stato della notifica. + 3. notifica + 3. icona della barra di stato + Widget mobile + Mostra glicemia, tendenza, delta, ora e IOB/COB come widget mobile.\nSe tieni premuto e non sposti il widget per più di 5 secondi, scomparirà. + Dimensione + Modifica la dimensione del widget mobile + Stile + Stile del widget mobile. + Trasparenza + Trasparenza dello sfondo per il widget mobile. + Trasparenza del widget + Trasparenza dello sfondo per i widget. + Tempo relativo + Utilizza il tempo relativo invece del timestamp della glicemia per i widget.\nIl tempo relativo dipende dalle impostazioni della batteria sul tuo dispositivo e potrebbe non funzionare correttamente. + + Icona dell\'app + Valore della glicemia + Freccia di tendenza + Delta + + Wear: vibrazione + + Trasmissione globale + + Mostra tutto + Nessun destinatario trovato! + + Glicemia + Glicemia e tendenza + Glicemia, tendenza e delta + Glicemia, tendenza, delta e timestamp + Glicemia, tendenza, delta, ora e IOB/COB + + Supporto BangleJS + Inoltra i valori al widget widbgjs per BangleJS. + + + + Ricezione dati per le complicazioni + + Telefono: connesso (%1$s) + Telefono: disconnesso + + In primo piano + Grande freccia di tendenza + AOD a colori + + + + Glicemia + Glicemia (molto grande e colorata) + Glicemia (colorata) + Glicemia (grande) + Glicemia con icona + Glicemia e tendenza + Glicemia e tendenza (grande e colorata) + Glicemia e tendenza (grande) + Glicemia e tendenza (colorata) + Glicemia e intervallo di tendenza (se supportato) + Glicemia, delta e intervallo di tendenza (se supportato) + Delta, freccia di tendenza e intervallo di tendenza (se supportato) + Glicemia e delta + Glicemia, delta e tendenza + Glicemia, delta, tendenza e timestamp + Glicemia, icona di tendenza, delta e timestamp + Glicemia e valore di tendenza + Delta (grande) + Delta (grande, colorato) + Delta + Delta e tendenza + Delta con icona + Delta e timestamp + Delta e timestamp (colorato) + Delta e timestamp (grande) + Tendenza (grande, colorata) + Tendenza (colorata) + Icona di tendenza + Complicazione intervallo di test con valori negativi (per il supporto dell\'intervallo di tendenza) + Valore di tendenza + Valore di tendenza e freccia + Valore di tendenza con icona + Timestamp della glicemia + Timestamp + Timestamp (colorato) + Timestamp (grande) + Livello batteria (orologio e telefono) + Livello batteria dell\'orologio + Livello batteria del telefono + IOB/COB + + + + https://github.com/pachi81/GlucoDataAuto/blob/main/README.md + Per utilizzare questa app in Android Auto, devi attivare le impostazioni sviluppatore in Android Auto facendo clic più volte sulla versione. Quindi devi abilitare \"Origini sconosciute\" nel menu delle impostazioni sviluppatore. + Informazioni + Se stai utilizzando GlucoDataHandler come sorgente, queste impostazioni verranno sovrascritte da GlucoDataHandler, quindi non devi modificarle qui. + GlucoDataAuto mancante + Per utilizzare GlucoDataHandler per Android Auto, devi installare GlucoDataAuto da GitHub.\nFai clic qui per ulteriori informazioni e per scaricare GlucoDataAuto. + Inoltra a GlucoDataAuto + Invia i valori della glicemia e le impostazioni a GlucoDataAuto, se il telefono è connesso ad Android Auto. + + + + + Notifica in primo piano + Notifica in primo piano che impedisce l\'arresto dell\'app durante l\'esecuzione in background. + Seconda notifica + Terza notifica + Notifica aggiuntiva per mostrare un\'icona extra nella barra di stato. + Notifica del worker + Notifica del worker visualizzata durante l\'esecuzione di attività in background. + Notifica in primo piano + Notifica in primo piano che impedisce l\'arresto dell\'app durante l\'esecuzione in background. + Notifica per Android Auto + Notifica visualizzata in Android Auto + Blocco rumore attivo nelle impostazioni Inter-app di xDrip+!\nSi prega di cambiarlo in \"Estremamente rumoroso\"! + Servizi cloud + Juggluco + Per ricevere valori da Juggluco:\n- apri Juggluco\n- vai su \"Impostazioni\"\n- abilita \"Trasmissione Glucodata\"\n- abilita \"de.michelinside.glucodatahandler\" + Per ricevere valori da Juggluco:\n- apri Juggluco\n- vai su \"Impostazioni\"\n- abilita \"Trasmissione Glucodata\"\n- abilita \"de.michelinside.glucodataauto\" + xDrip+ + Per ricevere valori da xDrip+:\n- apri xDrip+\n- vai su impostazioni\n- vai su Impostazioni Inter-app\n- abilita \"Trasmetti localmente\"\n- imposta \"Blocco rumore\" su \"Invia anche segnali estremamente rumorosi\" + - abilita \"Trasmissione compatibile\" - verifica che \"Identifica destinatario\" sia vuoto o, se esiste già una voce, aggiungi \"de.michelinside.glucodatahandler\" separato da uno spazio + - abilita \"Trasmissione compatibile\"\n- verifica che \"Identifica destinatari\" sia vuoto o aggiungi una nuova riga con \"de.michelinside.glucodataauto\" + Configura LibreLinkUp + IMPORTANTE: questo non è l\'account utilizzato per l\'app FreeStyle Libre!\nPer attivare il follower LibreLinkUp:\n- apri l\'app FreeStyle Libre e seleziona nel menu Condividi o App connesse\n- attiva la connessione LibreLinkUp\n- installa LibreLinkUp da Play Store\n- configura il tuo account e attendi l\'invito + Contatto + Supporto IOB/COB + Ricevi anche i valori IOB e COB dall\'endpoint Nightscout Pebble. + La notifica è obbligatoria per il corretto funzionamento dell\'applicazione.\nA partire da Android 13 è possibile scorrere via questa notifica. Se non si desidera che la notifica ritorni, impostare \"Icona della barra di stato\" su \"Icona dell\'app\" e abilitare \"Nascondi contenuto\". + AAPS + AndroidAPS + Per ricevere valori da AAPS:\n- apri l\'app AAPS\n- vai su \"Config Builder\"\n- abilita \"Samsung Tizen\" o \"Data Broadcaster\" + WatchDrip+ + \"Abilita la comunicazione WatchDrip+ (senza grafico).\nIMPORTANTE: abilita \"Abilita servizio\" in WatchDrip+ dopo aver abilitato questa impostazione qui!\" + Riappare + Intervallo in cui la notifica deve riapparire se non ci sono nuovi valori. (0 per mai). + Stile dell\'icona/immagine del lettore multimediale fittizio. + Stile icona/immagine + Telefono + Orologio + Salva log + Log salvati correttamente + Errore durante il salvataggio dei log! + Log da Wear salvati correttamente + Errore durante il salvataggio dei log da Wear! + Per tutto il lavoro relativo a orari/intervalli, questa app richiede l\'autorizzazione per allarmi e promemoria.\nNon aggiungerà o modificherà alcun promemoria utente, è solo per la pianificazione interna.\nSe premi OK, verrai inoltrato all\'impostazione delle autorizzazioni per abilitarla per GlucoDataHandler. + Autorizzazione per allarmi e promemoria + La pianificazione degli allarmi precisi è disabilitata!!!\nGlucoDataHandler potrebbe non funzionare correttamente!!!\nPremi qui per andare direttamente all\'impostazione delle autorizzazioni. + + Paziente + Se all\'account Libre sono connessi più pazienti, seleziona il paziente per il quale ricevere i dati. + Sfondo schermata di blocco + Abilita + Se abiliti lo sfondo della schermata di blocco, l\'app sostituisce lo sfondo sulla schermata di blocco. + Posizione verticale + Posizione verticale dell\'immagine della tendenza della glicemia sulla schermata di blocco:\n0 è la parte superiore del display\n100 è la parte inferiore del display + La visualizzazione delle notifiche è disabilitata!!!\nGlucoDataHandler potrebbe non funzionare correttamente e non può mostrare gli allarmi!!!\nPremi qui per andare direttamente all\'impostazione delle autorizzazioni. + + Dati mancanti! Accetta prima l\'invito nell\'app LibreLinkUp! + Icona della barra di stato colorata + Se il tuo dispositivo supporta le icone della barra di stato colorate, puoi anche disattivarle per utilizzare un elemento bianco. + + Allarmi + Intervallo + Intervallo minimo in minuti tra le notifiche per questo tipo di allarme.\nInformazioni: ci sarà una nuova notifica solo dopo che è trascorso il tempo, se il delta è negativo. + Intervallo minimo in minuti tra le notifiche per questo tipo di allarme.\nInformazioni: ci sarà una nuova notifica solo dopo che è trascorso il tempo, se il delta è positivo. + Tempo in minuti dall\'ultima ricezione del valore della glicemia per attivare questa notifica. Riapparirà anche in questo intervallo. + Abilitato + Le notifiche sono abilitate per questo tipo di allarme. + Le notifiche sono disabilitate per questo tipo di allarme. + Provalo + Premi qui per attivare una notifica dopo 3 secondi per questo tipo di allarme. + Impostazioni + Premi qui per andare alle impostazioni del canale di notifica per questo allarme.\nLì puoi modificare tutte le impostazioni correlate. + Salva suono + Premi qui se vuoi salvare il suono di allarme predefinito in un file sul tuo telefono. + Allarme salvato! + Posponi + Tutti gli allarmi + Notifiche di allarme + Wear: Notifiche di allarme + Le notifiche di allarme sono attive sul telefono.\nNota: gli allarmi utilizzano il volume di allarme del telefono. + Le notifiche di allarme sono inattive sul telefono. + Interrompi + Molto basso! + Basso! + Alto! + Molto alto! + Allarme molto basso + Notifica per allarmi molto bassi. + Allarme basso + Notifica per allarmi bassi. + Allarme alto + Notifica per allarmi alti. + Allarme molto alto + Notifica per allarmi molto alti. + Ignora + Allarme di prova! + Categorie di allarme + Notifica a schermo intero + Abilita la notifica a schermo intero sulla schermata di blocco.\nQuesto potrebbe richiedere un\'autorizzazione aggiuntiva. + Ignora la protezione tastiera + Abilita questa impostazione se hai problemi con la notifica a schermo intero.\nSu alcuni dispositivi, questo causerà la visualizzazione della schermata di sblocco mentre viene attivata la notifica a schermo intero.\nQuindi non abilitarla se non hai problemi. + Forza suono di allarme + Questo forzerà un suono di allarme e una vibrazione anche se il telefono è in modalità silenziosa o vibrazione. Interromperà sempre la modalità Non disturbare!\nPer abilitare questa impostazione, potrebbe esserti chiesto di consentire a questa app di modificare la modalità Non disturbare. + Wear: Forza suono di allarme + Forza vibrazione + Forza la vibrazione, anche se il telefono è in modalità silenziosa. Interromperà sempre la modalità Non disturbare!\nPer abilitare questa impostazione, potrebbe esserti chiesto di consentire a questa app di modificare la modalità Non disturbare. + Livello del suono + Forza l\'impostazione del livello del suono per il suono di allarme.\n-1 per utilizzare le impostazioni predefinite del telefono. + Avanzate + Impostazioni avanzate di allarme per notifiche a schermo intero e suoni. + Notifica + Suono + Impostazioni di allarme per i valori di glucosio a partire da %1$s. + Nessun allarme mentre Wear è connesso + Non ci sarà alcuna notifica di allarme sul telefono se il telefono è connesso a GlucoDataHandler sull\'orologio. + Ci sarà una notifica di allarme sul telefono, anche se il telefono è connesso a GlucoDataHandler sull\'orologio. + Nessun allarme mentre Android Auto è connesso + Non ci sarà alcuna notifica di allarme sul telefono se Android Auto è connesso. + Ci sarà una notifica di allarme sul telefono, anche se Android Auto è connesso. + Riattivazione + Se la notifica di allarme non viene chiusa, l\'allarme verrà attivato nuovamente dopo questo intervallo in minuti fino a 3 volte.\n0 - nessuna riattivazione + Obsoleto + Allarme obsoleto + Allarme se non ci sono nuovi valori di glucosio per %1$s minuti. + Questa notifica verrà visualizzata se non ci sono nuovi valori in un intervallo. + Nessun nuovo valore! + + La visualizzazione delle notifiche è disabilitata!!! + GlucoDataHandler necessita di notifiche per eseguire attività in background e per mostrare gli allarmi. + Notifica di allarme + Notifiche di allarme. Si prega di non modificare le impostazioni, poiché l\'app fornisce internamente suoni e vibrazioni per le notifiche. + Seleziona suono personalizzato + Silenzioso - nessun suono + Utilizza suono personalizzato + Utilizza il suono personalizzato selezionato. + Utilizza il suono fornito dall\'app. + Ritardo del suono + Tempo in secondi prima che il suono venga riprodotto.\nVibrerà per tutto il tempo, anche se non è stato selezionato alcun suono. + Suono e vibrazione + Solo vibrazione + Wear: Solo vibrazione + Notifica disabilitata! + Per mostrare gli allarmi, GlucoDataHandler necessita almeno del canale di notifica degli allarmi abilitato.\nVerrai reindirizzato alle impostazioni di notifica per questa app.\nAbilita il canale di notifica degli allarmi lì. + Suono interno dell\'app + Includi impostazioni + Includi le impostazioni nel messaggio a GlucoDataAuto.\nQuesto sovrascriverà le impostazioni in GlucoDataAuto. + Per ricevere valori da GlucoDataHandler:\n- apri GlucoDataHandler\n- vai su impostazioni\n- abilita \"Inoltra a GlucoDataAuto\"\n- per utilizzare le stesse impostazioni, abilita \"Includi impostazioni\" + Se utilizzi le notifiche di Android Auto, gli allarmi molto bassi attiveranno sempre una notifica e questo non può essere disattivato o modificato. + IOB da Juggluco + Per ricevere IOB da Juggluco:\n- apri Juggluco\n- vai su \"Impostazioni\"\n- abilita \"IOB\"\n- vai su \"Server Web\"\n- abilita \"Attivo\"\n- facoltativo: abilita \"Solo locale\" + IOB da xDrip+ + Per ricevere valori da xDrip+:\n- apri xDrip+\n- vai su impostazioni\n- vai su Impostazioni Inter-app\n- abilita \"Servizio Web xDrip\" + Attiva l\'utilizzo di IOB + Premi qui per impostare le impostazioni di Nightscout per utilizzare il server Web locale per ricevere valori IOB aggiuntivi. + Sovrascrivi Nightscout + Questo sovrascriverà le impostazioni correnti dell\'origine Nightscout per utilizzare il servizio Web locale sulla porta 17580 per ricevere valori IOB.\nSei sicuro? + Attiva/disattiva notifiche + Le notifiche sono attive + Le notifiche sono disattive + Premi play per attivare/disattivare la notifica + Le impostazioni di allarme sono relative solo alle notifiche su Android Auto e non attivano alcun suono. + Impostazioni generali, come le impostazioni di unità, ora e delta. + Impostazioni per i limiti di glucosio e i colori corrispondenti. + Impostazioni per i widget del telefono e il widget mobile. + Impostazioni di notifica. + Impostazioni per sostituire lo sfondo della schermata di blocco. + Impostazioni per GlucoDataAuto da utilizzare per Android Auto. + Orologi MiBand e Amazfit + Orologi Wear OS + Orologio + Impostazioni per gli smartwatch connessi. + Impostazioni per trasferire i valori di glucosio ad altre applicazioni. + Trasferisci valori + Visualizzazione + Orologi BangleJS + Per utilizzare GlucoDataHandler sugli orologi Wear OS, installalo da Google Play Store sull\'orologio. + Verifica connessione + Premi qui per ricontrollare la connessione a Wear OS.\nPuoi anche attivare questo controllo premendo sulle informazioni di connessione nella schermata iniziale (anche nell\'app Wear OS). + WatchDrip+ supporta diversi dispositivi MiBand e Amazfit.\nPer ottenere un elenco completo dei dispositivi supportati e maggiori informazioni sulla configurazione di WatchDrip+, premi qui. + Solo vibrazione senza notifica non funziona su tutti i dispositivi! + Utilizza il suono di allarme + Utilizza il volume del suono di allarme invece del suono della suoneria.\nQuesto potrebbe essere indipendente dalla modalità vibrazione. + Attiva questa opzione per forzare la non riproduzione di alcun suono di allarme, indipendentemente da qualsiasi altra impostazione del suono di allarme. + Ritardo di avvio + Ritardo prima che la vibrazione e il suono vengano riprodotti per la prima volta.\nA volte la notifica dal telefono interrompe immediatamente la vibrazione. + Colore valore obsoleto + Colore per nessun nuovo valore di glucosio. + Nessun popup mentre il telefono è connesso + Nessun popup di notifica aggiuntivo su Wear, mentre il telefono è connesso.\nLa vibrazione e il suono verranno attivati come configurato. + Dettagli + Dispositivi + disconnesso + connesso + Schema colori applicazione + Utilizza schema di sistema + Schema chiaro + Schema scuro + Azione al tocco + Seleziona un\'azione o un\'applicazione che dovrebbe essere eseguita toccando questo elemento. + Nessuna azione + Attiva/disattiva widget mobile + Azione al tocco della complicazione + Disabilita questa impostazione se la freccia di tendenza è troppo grande o ritagliata. + Interfaccia utente + Impostazioni generali per l\'interfaccia utente di questa applicazione. + GlucoDataAuto è in esecuzione + Abilitalo solo se utilizzi una sorgente follower e solo notifiche Android Auto.\nQuesto impedisce ad Android di chiudere questa app mentre non è connessa ad Android Auto, quindi l\'app non può riconoscere la connessione Android Auto.\nInvece di abilitare questa impostazione, puoi anche aprire l\'app in Android Auto una volta, se sei connesso. + L\'ottimizzazione della batteria è abilitata!\nPremi qui per disabilitarla. + Se utilizzi una sorgente follower, l\'app potrebbe non essere avviata mentre è connessa ad Android Auto, se utilizzi solo le notifiche di Android Auto.\nPer assicurarti che l\'app rilevi la connessione Android Auto, devi disabilitare l\'ottimizzazione della batteria.\nSe ancora non funziona, puoi anche abilitare la modalità in primo piano oppure devi aprire l\'app in Android Auto una volta, se è connessa ad Android Auto. + Configurazione di Android Auto + Android Auto è un\'app separata o parte del sistema e può essere accessibile tramite le impostazioni di Android.\nPer attivare GlucoDataAuto per Android Auto, devi eseguire i seguenti passaggi: + 1. Attiva la modalità sviluppatore + - apri Android Auto\n- scorri verso il basso fino alla versione\n- tocca più volte la versione finché non viene visualizzato un popup per \"Consenti impostazioni di sviluppo\"\n- premi \"OK\" + 2. Attiva \"Origini sconosciute\" + - apri Android Auto\n- apri nel menu a 3 punti le \"Impostazioni sviluppatore\"\n- scorri verso il basso fino a \"Origine sconosciuta\"\n- abilitala + 3. Imposta le impostazioni di notifica + - apri Android Auto\n- scorri verso il basso fino a \"Messaggi\"\n- abilita \"Mostra notifiche messaggi\"\n- abilita \"Mostra prima riga dei messaggi\" + 4. Abilita GlucoDataAuto + - apri Android Auto\n- scorri verso il basso fino a \"Display\"\n- apri \"Personalizza launcher\"\n- abilita \"GlucoDataAuto\"\nSe GlucoDataAuto non è disponibile, riavvia il telefono. + Layout colorato + Visualizza i valori in testo grande e colorato.\nSu alcuni dispositivi Samsung con Android 13 e versioni successive, questo potrebbe causare l\'arresto anomalo dell\'app a causa di un bug da parte di Samsung.\nSe si verifica il problema, questa impostazione verrà automaticamente disabilitata per garantire che l\'app rimanga funzionale. + Arresto anomalo rilevato! + Mi dispiace molto che l\'app si sia bloccata durante l\'esecuzione!\nInviami i registri in modo che io possa analizzare l\'errore e provare a risolverlo il prima possibile.\nGrazie! + Dexcom Share + Account USA + Viene utilizzato l\'account USA. + Viene utilizzato l\'account internazionale. + Abilita l\'account Dexcom Share. Devi prima impostare nome utente e password. + Nome utente + Nome utente, e-mail o numero di telefono per accedere al tuo account Dexcom Share.\nIMPORTANTE: se utilizzi il numero di telefono, deve includere il prefisso internazionale (ad esempio +1 per gli Stati Uniti). + Password per l\'account Dexcom Share. + Configura Dexcom Share + Autorizzazione mancante! + L\'impostazione \'%1$s\' è impostata, ma l\'autorizzazione correlata non è stata concessa! Premi \"OK\" per essere reindirizzato alla finestra di dialogo delle autorizzazioni o premi \"Annulla\" per disabilitare questa impostazione. + API del servizio di trasmissione + Utilizza l\'API del servizio di trasmissione xDrip+ per ricevere dati (anche IOB) da xDrip+.\nAttiva prima \"API del servizio di trasmissione\" in \"Impostazioni Inter-app\" in xDrip+! + Tempo di obsolescenza + Tempo in minuti dopo il quale il valore viene considerato obsoleto.\nDopo il doppio di questo tempo, il valore viene rimosso. + Applicazioni locali + Applicazioni sullo stesso dispositivo che forniscono valori di glucosio come messaggio di trasmissione interno, come Juggluco, xDrip+, AAPS e così via. + Servizi cloud che forniscono valori di glucosio tramite Internet, come LibreLinkUp, Dexcom Share e Nightscout. + Alternativa: trasmissione locale + Per ricevere valori da Dexcom BYODA, devi abilitare la trasmissione ad AAPS durante la creazione dell\'app. + Per ricevere valori da Eversense, devi utilizzare ESEL in modalità companion (lettura notifiche) o connesso all\'app Eversense con patch.\nPer ulteriori informazioni su ESEL, premi qui. + Per ricevere valori da Dexcom BYODA, devi abilitare la trasmissione a xDrip+ durante la creazione dell\'app. + La versione %s è disponibile + Note + Le notifiche sono disabilitate! \nPremi qui per abilitarle. + Mostra altra unità + Mostra il valore di glucosio in %s nei dettagli. + Altra unità + Premi qui per controllare i dati del tuo account Dexcom Share per il tuo account non statunitense. + Premi qui per controllare i dati del tuo account Dexcom Share per il tuo account statunitense. + Modifica le dimensioni dei dati della schermata di blocco. + Ripristina connessione + Premi qui per ripristinare la connessione tra orologio e telefono se non funziona come previsto.\nSe il ripristino non risolve il problema, riavvia l\'orologio. + La connessione non funziona come previsto! Prova a riavviare l\'orologio. + Tempo per chiudere + Tempo in secondi per chiudere il widget mobile premendo sul widget senza spostarlo.\n0 per disabilitare questa impostazione. + Abilita le ore di silenzio + Nessuna notifica di allarme per questo allarme durante le ore di silenzio.\nConfigura gli orari preferiti per utilizzare questa funzione. + Nessuna notifica di allarme per tutti gli allarmi durante le ore di silenzio.\nConfigura gli orari preferiti per utilizzare questa funzione. + Ora di inizio + Seleziona l\'ora in cui desideri che inizino le ore di silenzio. + Ora di fine + Seleziona l\'ora in cui desideri che finiscano le ore di silenzio. + Giorni per le ore di silenzio + Specifica i giorni della settimana in cui le ore di silenzio dovrebbero iniziare all\'ora di inizio selezionata. + L\'app si arresta in modo anomalo a causa del layout di notifica colorato!\nÈ stato disabilitato per evitare questo arresto anomalo.\nUn riavvio del telefono di solito risolve questo problema. + Chiusura automatica notifica + Chiudi automaticamente la notifica di allarme dopo che il suono e la vibrazione sono terminati, ma solo dopo almeno 30 secondi.\nSe la riattivazione è attiva, la notifica verrà chiusa se non ci sono trigger aggiuntivi. + + Impostazioni salvate correttamente + Errore durante il salvataggio delle impostazioni! + Impostazioni lette correttamente + Errore durante la lettura delle impostazioni! + Esporta / Importa + Esporta o importa impostazioni e salva i registri. + Esporta + Premi qui per esportare tutte le impostazioni in un file. \nIMPORTANTE: questo esporterà solo le impostazioni modificate! Le impostazioni con valori predefiniti non faranno parte dell\'esportazione. + Importa + Premi qui per importare le impostazioni da un file.\nIMPORTANTE: questo non ricreerà le impostazioni predefinite, poiché non fanno parte dell\'esportazione.\nSe desideri ripristinare le impostazioni predefinite, elimina prima i dati dell\'app e importa in seguito. + Salva i registri dal telefono in un file. + Salva i registri dall\'orologio Wear OS connesso in un file. + Ripeti + Nessuna vibrazione + Modalità di vibrazione + Aumento rapido + Allarme aumento rapido + Allarme se il valore di glucosio è superiore o uguale a %1$s e il delta aumenta di almeno +%2$s per %3$d letture consecutive. + Aumento rapido! + + Rapida diminuzione + Allarme rapida diminuzione + Allarme se il valore di glucosio è inferiore o uguale a %1$s e il delta diminuisce di almeno -%2$s per %3$d letture consecutive. + Rapida diminuzione! + Intensità della vibrazione + Intensità della vibrazione + Impostazioni di allarme + Valore delta quando l\'allarme dovrebbe essere attivato. + Numero di occorrenze + Numero di volte in cui il valore delta deve essere raggiunto consecutivamente. + Limite + Limite per l\'allarme di aumento/diminuzione.\nIl valore di glucosio deve essere superiore/inferiore per attivare l\'allarme. + al minuto + ogni 5 minuti + Altri allarmi + Impostazioni avanzate di allarme + I messaggi interni da questa fonte verranno gestiti. + I messaggi interni da questa fonte verranno ignorati. + Temporaneamente disabilitato + Rimandato fino a + Temporaneamente disabilitato fino a + + + Tendenza + %1$d gradi in aumento + %1$d gradi in diminuzione + + Allarmi: %1$s + inattivo + rimandato fino a %1$s + temporaneamente disabilitato fino a %1$s + + abilitato + disabilitato + non disponibile + Leggi valori + + Tempo %d minuto + Tempo %d minuti + + Apri Integrazione e abilita \"Condividi dati con GWatch\"\nNota: la trasmissione DiaBox contiene solo il valore del glucosio e nessuna informazione aggiuntiva come la tendenza! + Nota: la trasmissione AAPS da BYODA non sembra funzionare con altre app. \nUtilizza la trasmissione xDrip+ in BYODA. + Inizia con GDA su Android Auto + Sintesi vocale (TTS) non abilitata.\nPer utilizzare questa funzione, abilita TTS nelle impostazioni di accessibilità di Android.\nUna volta abilitato, tocca qui per aggiornare. + Pronuncia valore + Riproduzione + Pronuncia il valore corrente durante la riproduzione.\nRichiede l\'abilitazione della sintesi vocale. + Riproduci un suono vuoto per portare il lettore multimediale in primo piano nella schermata divisa. + Pronuncia nuovo valore + Pronuncia nuovi valori in Android Auto, anche quando si utilizza un altro lettore multimediale. + Tocca per testare la sintesi vocale. + Pronuncia solo gli allarmi. + Imposta l\'intervallo per la pronuncia di nuovi valori in Android Auto. + L\'intervallo per la ricezione di nuovi valori di glucosio viene utilizzato come durata per la barra di avanzamento nel lettore multimediale.\nImpostando il valore su 0 si disabilita la barra di avanzamento. + Attiva/disattiva pronuncia nuovo valore + Pronuncia attiva + Pronuncia disattiva + Abilita icona allarme + Abilita l\'icona di allarme nella schermata principale per attivare/disattivare lo stato di allarme. + + + + Errore, accedi nuovamente all\'app LibreLinkUp e verifica se ci sono passaggi da eseguire! + Errore di tipo %1$s, accedi nuovamente all\'app LibreLinkUp e verifica se ci sono passaggi da eseguire! + Accetta automaticamente i nuovi termini + Abilitando questa opzione, accetti automaticamente i nuovi Termini di utilizzo per garantire la ricezione continua dei dati. Non sarai informato separatamente sui nuovi termini. Se desideri leggerli, disabilita questa impostazione e fai riferimento all\'app LibreLinkUp in caso di errore 4. + È necessario accettare i nuovi Termini di utilizzo. Abilita l\'accettazione automatica nelle impostazioni di origine di LibreLinkUp o accedi nuovamente all\'app LibreLinkUp e accetta i termini. + GlucoDataHandler può accettare automaticamente i nuovi Termini di utilizzo per LibreLinkUp.\nPremi OK per abilitare questa funzione o Annulla per leggere e accettare manualmente i termini nell\'app LibreLinkUp. + Nome utente o password errati. Verifica le impostazioni di origine di LibreLinkUp. Se necessario, prova ad accedere di nuovo o reinstalla l\'app LibreLinkUp e controlla le impostazioni del tuo account.\nInterrompi tutte le altre app che accedono allo stesso account! + Ultima lettura della glicemia sul server da %1$s + Nessuna lettura della glicemia trovata sul server. + Troppe richieste! + Interrompi tutte le altre app che accedono allo stesso account! + In attesa di dati + Invia livello batteria + Invia il livello della batteria all\'orologio per visualizzarlo nella complicazione e nella schermata principale. + Mostra livello batteria + Imposta il tempo minimo (in minuti) tra le misurazioni della glicemia prima di inviare un nuovo aggiornamento all\'orologio.\nGli allarmi e le misurazioni molto basse verranno sempre inviati. + Imposta il tempo minimo (in minuti) tra le misurazioni della glicemia prima di inviare un nuovo aggiornamento ai destinatari. + L\'allarme suonerà e vibrerà ripetutamente finché non si chiude la notifica o non viene raggiunta la durata massima impostata per l\'allarme. + Durata massima allarme + Specifica il tempo massimo (in minuti) in cui l\'allarme suonerà e vibrerà. Un valore di 0 significa che l\'allarme si ripeterà indefinitamente. + Per abilitare le origini, configurale prima nell\'app del telefono. Attiva le origini sull\'orologio solo se non ricevi ancora i dati sul telefono. + Nota dello sviluppatore + Questa app è stata sviluppata nel mio tempo libero come progetto hobby. Pertanto, non posso garantire che tutte le funzionalità funzionino perfettamente su ogni dispositivo. Tuttavia, lavoro costantemente a miglioramenti.\nSe riscontri problemi o hai domande, contattami direttamente prima di lasciare una recensione negativa. \nPer rimanere informato su importanti aggiornamenti e correzioni di bug, sei invitato a unirti al gruppo Facebook o ai Gruppi Google. + + Per ricevere dati dai server Dexcom Share:\nNell\'app Dexcom:\n - Crea un follower\n - In "Connessioni", assicurati che sia visualizzato "Condividi" + In GlucoDataHandler:\n - Inserisci il tuo nome utente e password di Dexcom Clarity\n (se usi il tuo numero di telefono come nome utente, includi il prefisso internazionale)\n - Controlla l\'impostazione per un account USA\nImportante: questo non funziona con un account follower! + + Aggiornato + Allarmi + + Quadranti di orologio + Informazioni sui quadranti di terze parti per Wear OS. + Configurazione + Quadranti di terze parti + Questa app non fornisce un quadrante, ma diverse complicazioni (le piccole parti rotonde del quadrante), che possono essere utilizzate per quadranti che supportano complicazioni di terze parti. + Standard + La maggior parte dei produttori di orologi fornisce già quadranti che possono essere utilizzati con la complicazione GlucoDataHandler.\nAd esempio, la maggior parte dei quadranti "Info" di Samsung sono compatibili. + Pujie + Pujie è un\'app per la creazione di quadranti con una libreria cloud.\nPer trovare quadranti compatibili, cerca nella libreria i quadranti che iniziano con "GDH_".\nNota: Pujie non è compatibile con i dispositivi Wear OS 5.\nTocca qui per aprire l\'app nel Play Store. + Diabetic Masked Man + Diabetic Masked Man crea quadranti di terze parti progettati specificamente per l\'uso con GlucoDataHandler.\nTocca qui per aprire la sua pagina del Play Store. + Quadranti GDC + I quadranti GDC sono progettati per l\'uso con GlucoDataHandler.\nTocca qui per aprire la pagina del Play Store. + Aggiorna sempre le complicazioni + Invia sempre nuovi valori di glucosio all\'orologio.\nDisabilita questa opzione per impedire gli aggiornamenti dei dati sull\'orologio quando lo schermo dell\'orologio è spento per risparmiare batteria.\nI valori di allarme verranno sempre inviati all\'orologio.\nIMPORTANTE: l\'allarme obsoleto viene disabilitato sull\'orologio quando questa impostazione è disabilitata! + Avvisa sempre per valori molto bassi + Questo allarme ti avviserà anche se la funzione snooze o le ore di silenzio generali per tutti gli allarmi sono attive, assicurandoti di essere avvisato in caso di livelli di glucosio molto bassi. + + Se questo errore persiste, prova a reinstallare l\'app Libre. Questo spesso risolve il problema. + Server + Predefinito (.io) + Russo (.ru) + + Nome + Nome dell\'utente/paziente, che dovrebbe essere mostrato nella notificazione e durante la lettura.\nQuesto dovrebbe essere usato se si utilizzano più varianti di GlucoDataAuto, altrimenti potrebbe essere vuoto. + + Video tutorial + Fai clic qui per andare al video di YouTube che illustra la configurazione di questa fonte. Mille grazie a Diabetic Masked Man per aver creato i video. + + 30 minuti + 60 minuti + 90 minuti + 120 minuti + Posponi sulla notifica + Scegli fino a 3 durate di snooze da visualizzare come pulsanti sulla notifica di allarme. + + Mostra valori IOB/COB + Mostra i valori IOB e COB, se disponibili. + + diff --git a/common/src/main/res/values-nl/strings.xml b/common/src/main/res/values-nl/strings.xml new file mode 100644 index 000000000..6792a9871 --- /dev/null +++ b/common/src/main/res/values-nl/strings.xml @@ -0,0 +1,720 @@ + + + nl + + Sensor + Waarde + Ruw + Delta + Snelheid + Tijd + IOB/COB tijd + Tijdsverschil + Alarm + Bron + per minuut + Geen gegevens ontvangen!\nSelecteer eerst een bron + + %1$d min + \> 1 uur + + stijgt zeer snel + stijgt snel + stijgt + stabiel + daalt + daalt snel + daalt zeer snel + + OK + Annuleren + + - + zeer laag + laag + hoog + zeer hoog + + + + Batterijoptimalisatie is ingeschakeld + Bron: niet actief + Wear: verbonden (%1$s) + Wear: niet verbonden + Android Auto: verbonden + Android Auto: niet verbonden + Hoog contrast toegankelijkheid is actief!\nDruk hier om naar instellingen te gaan. + Geen nieuwe waarde sinds %1$d minuten! + + Instellingen + Bronnen + GitHub + Updates + Ondersteuning + Help + + + + Nieuwe glucosewaarde + Verouderde waarde + Glucosealarm + Wear-verbindingsstatus + Android Auto-verbindingsstatus + Wijziging glucosebereik + + + + Telefoon + Wear + + Niet actief + Bezig + OK + Geen nieuwe waarde + Geen internetverbinding + Fout + + Interval + Interval voor het opvragen van gegevens van de bron. + Vertraging + Vertraging in seconden voordat de gegevens uit de cloud worden opgevraagd, omdat het enige tijd duurt voordat de gegevens daar aanwezig zijn. + + LibreLinkUp + Inschakelen + Schakel LibreLinkUp-volger in als gebruikersnaam en wachtwoord zijn ingesteld. + E-mailadres + E-mailadres voor LibreLinkUp login + Wachtwoord + Wachtwoord voor LibreLinkUp + Opnieuw verbinden + Bij het volgende interval wordt een nieuwe login uitgevoerd. + + Nightscout + Inschakelen + Schakel Nightscout-volger in. + Nightscout URL + URL van de Nightscout-server. + API-geheim + API-geheim voor toegang tot de gegevens (optioneel). + Toegangstoken + Voeg uw toegangstoken toe, indien nodig (optioneel). + + Altijd + 1 minuut + 5 minuten + 10 minuten + 15 minuten + 20 minuten + 30 minuten + Alleen alarm + + + + uitgeschakeld + niet verbonden + verbonden + fout: %s + + + + + Algemeen + Melding + Widgets + Android Auto + Glucosewaarden doorsturen + Doelbereik + Kleuren + + Dummy waarden + Zeer hoge glucose + Grens voor zeer hoge glucosewaarden. + Hoge glucose + Grens voor hoge glucosewaarden. + Doel bovengrens glucose + Bovengrens voor het doelbereik. + Doel ondergrens glucose + Ondergrens voor het doelbereik. + Zeer lage glucose + Grens voor zeer lage glucosewaarde. + Lage glucose + Grens voor lage glucosewaarde. + Android Auto-meldingen + Melding voor Android Auto. + Meldingsinterval + Minimuminterval voor het weergeven van meldingen voor een nieuwe waarde.\nAlarmen zullen altijd een melding weergeven. + Alleen alarm + Zal alleen meldingen activeren voor alarmen in hun specifieke interval.\nZeer lage waarden zullen altijd een melding weergeven. + Nep-mediaspeler + Toon de glucosewaarde als huidig nummer voor Android Auto. + Glucodata-broadcast verzenden + Glucodata-broadcast verzenden voor nieuwe waarden die van een bron zijn ontvangen. + Glucodata-ontvangers identificeren + Kies ontvangers waarnaar de glucodata-broadcast moet worden verzonden. Een globale broadcast wordt verzonden naar elke app die zich dynamisch registreert op de broadcast. + Gepatchte libre-broadcast verzenden + Stuur glucosewaarden door naar xDrip+. Selecteer \'Libre (gepatchte app)\' als bron in xDrip+. + Gepatchte libre-ontvangers identificeren + Kies ontvangers waarnaar de gepatchte libre-broadcast moet worden verzonden. Een globale broadcast wordt verzonden naar elke app die zich dynamisch registreert op de broadcast. + xDrip+-broadcast verzenden + Verzend broadcast zoals xDrip+ verzendt. Bijvoorbeeld naar AAPS. + xDrip+-broadcastontvangers identificeren + Kies ontvangers waarnaar de xDrip+-broadcast moet worden verzonden. Een globale broadcast wordt verzonden naar elke app die zich dynamisch registreert op de broadcast. + Gebruik mmol/l + Gebruik mmol/l in plaats van mg/dl. + 5 minuten delta + Schakel dit in om deltawaarden te gebruiken voor een interval van 5 minuten, anders wordt een interval van 1 minuut gebruikt. + Kleur zeer hoog/laag + Kleur voor zeer hoge en zeer lage glucosewaarden. + Kleur hoog/laag + Kleur voor hoge en lage glucosewaarden. + Doelkleur + Kleur voor glucosewaarden binnen het doelbereik. + Melding + Toont een permanente melding met de huidige trendpijl, glucose- en deltawaarde in de statusbalk.\nDeze melding voorkomt dat Android deze app stopt. + Statusbalkpictogram + Stijl van het kleine statusbalkpictogram van de melding. + Groot statusbalkpictogram + Groter pictogram in de statusbalk. Schakel deze instelling uit als het pictogram te groot is en wordt afgesneden. + Inhoud verbergen + Dit verwijdert de inhoud van de melding, zodat er alleen een kleine melding met het bijbehorende pictogram is. + 2e melding + Toon een melding zonder inhoud om een extra pictogram in de statusbalk te hebben. + 2e statusbalkpictogram + Stijl van het kleine statusbalkpictogram van de melding. + 3e melding + 3e statusbalkpictogram + Zwevende widget + Toon glucose, trend, delta, tijd en IOB/COB als zwevende widget.\nAls u de widget langer dan 5 seconden vasthoudt en niet beweegt, verdwijnt deze. + Grootte + Wijzig de grootte van de zwevende widget + Stijl + Stijl van de zwevende widget. + Transparantie + Achtergrondtransparantie voor zwevende widget. + Widgettransparantie + Achtergrondtransparantie voor widgets. + Relatieve tijd + Gebruik relatieve tijd in plaats van glucosetijdstip voor widgets.\nRelatieve tijd is afhankelijk van de batterij-instellingen op uw apparaat en werkt mogelijk niet correct. + + App-pictogram + Glucosewaarde + Trendpijl + Delta + + Wear: trillen + + Globale broadcast + + Alles tonen + Geen ontvanger gevonden! + + Glucose + Glucose en trend + Glucose, trend en delta + Glucose, trend, delta en tijd + Glucose, trend, delta, tijd en IOB/COB + + BangleJS-ondersteuning + Stuur waarden door naar widbgjs-widget voor BangleJS. + + + + Gegevens ontvangen voor complicaties + + Telefoon: verbonden (%1$s) + Telefoon: niet verbonden + + Voorgrond + Grote trendpijl + Gekleurde AOD + + + + Glucose + Glucose (zeer groot en gekleurd) + Glucose (gekleurd) + Glucose (groot) + Glucose met pictogram + Glucose en trend + Glucose en trend (groot en gekleurd) + Glucose en trend (groot) + Glucose en trend (gekleurd) + Glucose- en trendbereik (indien ondersteund) + Glucose, delta en trendbereik (indien ondersteund) + Delta, trendpijl en trendbereik (indien ondersteund) + Glucose en delta + Glucose, delta en trend + Glucose, delta, trend en tijd + Glucose, trendpictogram, delta en tijd + Glucose en trendwaarde + Delta (groot) + Delta (groot, gekleurd) + Delta + Delta en trend + Delta met pictogram + Delta en tijd + Delta en tijd (gekleurd) + Delta en tijd (groot) + Trend (groot, gekleurd) + Trend (gekleurd) + Trendpictogram + Testbereikcomplicatie met negatieve waarden (voor ondersteuning van trendbereik) + Trendwaarde + Trendwaarde en pijl + Trendwaarde met pictogram + Glucosetijd + Tijd + Tijd (gekleurd) + Tijd (groot) + Batterijniveau (horloge en telefoon) + Batterijniveau horloge + Batterijniveau telefoon + IOB/COB + + + + https://github.com/pachi81/GlucoDataAuto/blob/main/README.md + Om deze app in Android Auto te gebruiken, moet u de ontwikkelaarsinstellingen in Android Auto activeren door meerdere keren op de versie te klikken. Vervolgens moet u \"Onbekende bronnen\" inschakelen in het menu met ontwikkelaarsinstellingen. + Informatie + Als u GlucoDataHandler als bron gebruikt, worden deze instellingen overschreven vanuit GlucoDataHandler, dus u hoeft ze hier niet te wijzigen. + GlucoDataAuto ontbreekt + Om GlucoDataHandler voor Android Auto te gebruiken, moet u GlucoDataAuto van GitHub installeren.\nKlik hier voor meer informatie en om GlucoDataAuto te downloaden. + Doorsturen naar GlucoDataAuto + Verzend glucosewaarden en instellingen naar GlucoDataAuto als de telefoon is verbonden met Android Auto. + + + + + Voorgrondmelding + Voorgrondmelding die voorkomt dat de app wordt gestopt terwijl deze op de achtergrond wordt uitgevoerd. + Tweede melding + Derde melding + Extra melding om een extra pictogram in de statusbalk weer te geven. + Werkermelding + Werkermelding die wordt weergegeven tijdens het uitvoeren van taken op de achtergrond. + Voorgrondmelding + Voorgrondmelding die voorkomt dat de app wordt gestopt terwijl deze op de achtergrond wordt uitgevoerd. + Melding voor Android Auto + Melding die wordt weergegeven in Android Auto + Noise-Block actief in xDrip+ Inter-app-instellingen!\nWijzig dit in \"Extreem luid\"! + Cloudservices + Juggluco + Om waarden van Juggluco te ontvangen:\n- open Juggluco\n- ga naar \"Instellingen\"\n- schakel \"Glucodata-broadcast\" in\n- schakel \"de.michelinside.glucodatahandler\" in + Om waarden van Juggluco te ontvangen:\n- open Juggluco\n- ga naar \"Instellingen\"\n- schakel \"Glucodata-broadcast\" in\n- schakel \"de.michelinside.glucodataauto\" in + xDrip+ + Om waarden van xDrip+ te ontvangen:\n- open xDrip+\n- ga naar instellingen\n- ga naar Inter-app-instellingen\n- schakel \"Lokaal uitzenden\" in\n- stel \"Ruisblokkering\" in op \"Verzend zelfs extreem luide signalen\" + - schakel \"Compatibele broadcast\" in - controleer of \"Ontvanger identificeren\" leeg is of als er al een item bestaat, voeg \"de.michelinside.glucodatahandler\" toe, gescheiden door een spatie + - schakel \"Compatibele broadcast\" in\n- controleer of \"Ontvangers identificeren\" leeg is of voeg een nieuwe regel toe met \"de.michelinside.glucodataauto\" + LibreLinkUp instellen + BELANGRIJK: dit is niet het account dat wordt gebruikt voor de FreeStyle Libre app!\nOm LibreLinkUp-volger te activeren:\n- open uw FreeStyle Libre app en selecteer in het menu Delen of Verbonden apps\n- activeer LibreLinkUp-verbinding\n- installeer LibreLinkUp vanuit de Play Store\n- stel uw account in en wacht op de uitnodiging + Contact + IOB/COB ondersteuning + Ontvang ook IOB en COB waarden van het Nightscout Pebble-eindpunt. + De melding is verplicht voor een correcte werking van de applicatie.\nVanaf Android 13 kunt u deze melding wegvegen. Als u de melding niet terug wilt, stelt u het \"Statusbalkpictogram\" in op \"App-pictogram\" en schakelt u \"Inhoud verbergen\" in. + AAPS + AndroidAPS + Om waarden van AAPS te ontvangen:\n- open de AAPS-app\n- ga naar \"Config Builder\"\n- schakel \"Samsung Tizen\" of \"Data Broadcaster\" in + WatchDrip+ + "Schakel WatchDrip+ communicatie in (zonder grafiek).\nBELANGRIJK: schakel \"Service inschakelen\" in WatchDrip+ in nadat u deze instelling hier hebt ingeschakeld!" + Opnieuw verschijnen + Interval wanneer de melding opnieuw moet verschijnen als er geen nieuwe waarde is. (0 voor nooit). + Stijl van het dummy mediaspelerpictogram/afbeelding. + Pictogram/afbeeldingsstijl + Mobiel + Wear + Logboeken opslaan + Logboeken succesvol opgeslagen + Logboeken opslaan mislukt! + Logboeken van Wear succesvol opgeslagen + Logboeken van Wear opslaan mislukt! + Voor alle tijd/interval gerelateerde taken heeft deze app de toestemming voor alarmen en herinneringen nodig.\nHet zal geen gebruikersherinneringen toevoegen of wijzigen, het is alleen voor interne planning.\nAls u op OK drukt, wordt u doorgestuurd naar de toestemmingsinstelling om deze in te schakelen voor GlucoDataHandler. + Toestemming voor alarmen en herinneringen + Planning van exacte alarmen is uitgeschakeld!!!\nGlucoDataHandler werkt mogelijk niet correct!!!\nDruk hier om direct naar de toestemmingsinstelling te gaan. + + + Patiënt + Als er meer dan één patiënt is verbonden met het Libre-account, selecteer dan de patiënt waarvoor de gegevens worden ontvangen. + Vergrendelschermachtergrond + Inschakelen + Als u de vergrendelschermachtergrond inschakelt, vervangt de app de achtergrond op het vergrendelscherm. + Verticale positie + Verticale positie van de glucose-trendafbeelding op het vergrendelscherm:\n0 is de bovenkant van het scherm\n100 is de onderkant van het scherm + Het weergeven van meldingen is uitgeschakeld!!!\nGlucoDataHandler werkt mogelijk niet correct en kan geen alarmen weergeven!!!\nDruk hier om direct naar de toestemmingsinstelling te gaan. + + Ontbrekende gegevens! Accepteer eerst de uitnodiging in de LibreLinkUp app! + Gekleurd statusbalkpictogram + Als uw apparaat gekleurde statusbalkpictogrammen ondersteunt, kunt u deze ook deactiveren om een wit item te gebruiken. + + Alarmen + Interval + Minimale interval in minuten tussen meldingen voor dit alarmtype.\nInformatie: er zal alleen een nieuwe melding zijn nadat de tijd is verstreken, als de delta negatief is. + Minimale interval in minuten tussen meldingen voor dit alarmtype.\nInformatie: er zal alleen een nieuwe melding zijn nadat de tijd is verstreken, als de delta positief is. + Tijd in minuten sinds de laatste glucosewaarde is ontvangen om deze melding te activeren. Het zal ook opnieuw verschijnen in dit interval. + Ingeschakeld + Meldingen zijn ingeschakeld voor dit alarmtype. + Meldingen zijn uitgeschakeld voor dit alarmtype. + Test het + Druk hier om na 3 seconden een melding voor dit alarmtype te activeren. + Instellingen + Druk hier om naar de instellingen voor het meldingskanaal voor dit alarm te gaan.\nDaar kunt u alle gerelateerde instellingen wijzigen. + Geluid opslaan + Druk hier als u het standaard alarmgeluid wilt opslaan in een bestand op uw telefoon. + Alarm opgeslagen! + Snoozen + Alle alarmen + Alarmmeldingen + Wear: Alarmmeldingen + Alarmmeldingen zijn actief op de telefoon.\nOpmerking: alarmen gebruiken het alarmvolume van de telefoon. + Alarmmeldingen zijn inactief op de telefoon. + Stoppen + Zeer laag! + Laag! + Hoog! + Zeer hoog! + Zeer laag alarm + Melding voor zeer lage alarmen. + Laag alarm + Melding voor lage alarmen. + Hoog alarm + Melding voor hoge alarmen. + Zeer hoog alarm + Melding voor zeer hoge alarmen. + Sluiten + Testalarm! + Alarmcategorieën + Volledig schermmelding + Schakelt volledig schermmelding op vergrendelscherm in.\nDit kan om een extra toestemming vragen. + Vergrendelscherm sluiten + Schakel deze instelling in als u problemen ondervindt met de volledig schermmelding.\nOp sommige apparaten zal dit ervoor zorgen dat het ontgrendelingsscherm wordt weergegeven terwijl de volledig schermmelding wordt geactiveerd.\nSchakel het dus niet in als u geen problemen ondervindt. + Alarmgeluid forceren + Dit zal een alarmgeluid en trilling forceren, zelfs als de telefoon in stille of trilmodus staat. Het zal altijd de modus Niet storen stoppen!\nOm deze instelling in te schakelen, wordt u mogelijk gevraagd om deze app toe te staan de modus Niet storen te wijzigen. + Wear: Alarmgeluid forceren + Trilling forceren + Forceer trilling, zelfs als de telefoon in stille modus staat. Het zal altijd de modus Niet storen stoppen!\nOm deze instelling in te schakelen, wordt u mogelijk gevraagd om deze app toe te staan de modus Niet storen te wijzigen. + Geluidsniveau + Forceer het instellen van het geluidsniveau voor het alarmgeluid.\n-1 voor het gebruik van de standaardinstellingen van de telefoon. + Geavanceerd + Geavanceerde alarminstellingen voor volledig schermmelding en geluiden. + Melding + Geluid + Alarminstellingen voor glucosewaarden vanaf %1$s. + Geen alarm terwijl Wear is verbonden + Er zal geen alarmmelding op de telefoon verschijnen als de telefoon is verbonden met GlucoDataHandler op het horloge. + Er zal een alarmmelding op de telefoon verschijnen, zelfs als de telefoon is verbonden met GlucoDataHandler op het horloge. + Geen alarm terwijl Android Auto is verbonden + Er zal geen alarmmelding op de telefoon verschijnen als Android Auto is verbonden. + Er zal een alarmmelding op de telefoon verschijnen, zelfs als Android Auto is verbonden. + Opnieuw activeren + Als de alarmmelding niet wordt gesloten, wordt het alarm na dit interval in minuten maximaal 3 keer opnieuw geactiveerd.\n0 - niet opnieuw activeren + Verouderd + Verouderd alarm + Alarm als er gedurende %1$s minuten geen nieuwe glucosewaarde is. + Deze melding verschijnt als er gedurende een interval geen nieuwe waarde is. + Geen nieuwe waarde! + + Het weergeven van meldingen is uitgeschakeld!!! + GlucoDataHandler heeft meldingen nodig om taken op de achtergrond uit te voeren en om alarmen weer te geven. + Alarmmelding + Alarmmeldingen. Wijzig de instellingen niet, aangezien de app intern geluid en trillingen voor meldingen biedt. + Selecteer aangepast geluid + Stil - geen geluid + Gebruik aangepast geluid + Gebruik aangepast geselecteerd geluid. + Gebruik door de app geleverd geluid. + Geluidsvertraging + Tijd in seconden voordat het geluid wordt afgespeeld.\nHet zal de hele tijd trillen, zelfs als er geen geluid is geselecteerd. + Geluid en trillingen + Alleen trillen + Wear: Alleen trillen + Melding uitgeschakeld! + Om alarmen weer te geven, moet GlucoDataHandler ten minste het alarmmeldingskanaal ingeschakeld hebben.\nU wordt doorgestuurd naar de meldingsinstelling voor deze app.\nSchakel daar het alarmmeldingskanaal in. + Intern app-geluid + Instellingen opnemen + Neem instellingen op in bericht naar GlucoDataAuto.\nDit zal de instellingen in GlucoDataAuto overschrijven. + Om waarden van GlucoDataHandler te ontvangen:\n- open GlucoDataHandler\n- ga naar instellingen\n- schakel \"Doorsturen naar GlucoDataAuto\" in\n- om dezelfde instellingen te gebruiken, schakel \"Instellingen opnemen\" in + Als u Android Auto-meldingen gebruikt, zullen zeer lage alarmen altijd een melding activeren en dit kan niet worden gedeactiveerd of gewijzigd. + IOB van Juggluco + Om IOB van Juggluco te ontvangen:\n- open Juggluco\n- ga naar \"Instellingen\"\n- schakel \"IOB\" in\n- ga naar \"Webserver\"\n- schakel \"Actief\" in\n- optioneel: schakel \"Alleen lokaal\" in + IOB van xDrip+ + Om waarden van xDrip+ te ontvangen:\n- open xDrip+\n- ga naar instellingen\n- ga naar Inter-app-instellingen\n- schakel \"xDrip-webservice\" in + IOB-gebruik activeren + Druk hier om Nightscout-instellingen in te stellen om de lokale webserver te gebruiken om extra IOB-waarden te ontvangen. + Nightscout overschrijven + Dit zal uw huidige Nightscout-broninstellingen overschrijven om lokale webservice op poort 17580 te gebruiken om IOB-waarden te ontvangen.\nWeet u het zeker? + Meldingsschakelaar + Meldingen staan aan + Meldingen staan uit + Druk op afspelen om de melding in te schakelen + Alarminstellingen zijn alleen gerelateerd aan meldingen op Android Auto en activeren geen geluid. + Algemene instellingen, zoals de eenheid, tijd en delta-instellingen. + Instellingen voor glucosegrenzen en de bijbehorende kleuren. + Instellingen voor telefoonwidgets en zwevende widget. + Meldingsinstellingen. + Instellingen om de vergrendelschermachtergrond te vervangen. + Instelling voor GlucoDataAuto voor gebruik met Android Auto. + MiBand en Amazfit horloges + Wear OS horloges + Horloge + Instellingen voor verbonden smartwatches. + Instellingen om glucosewaarden over te dragen naar andere applicaties. + Waarden overdragen + Visualisatie + BangleJS horloges + Om GlucoDataHandler op Wear OS horloges te gebruiken, installeert u deze vanuit de Google Play Store op het horloge. + Verbinding controleren + Druk hier om de verbinding met Wear OS opnieuw te controleren.\nU kunt deze controle ook activeren door op de verbindingsinfo op het startscherm te drukken (zelfs in de Wear OS app). + WatchDrip+ ondersteunt verschillende MiBand en Amazfit apparaten.\nOm een volledige lijst met ondersteunde apparaten en meer informatie over het configureren van WatchDrip+ te krijgen, drukt u hier. + Alleen trillen zonder melding werkt niet op elk apparaat! + Gebruik alarmgeluid + Gebruik het volume van het alarmgeluid in plaats van het beltoon geluid.\nDit kan onafhankelijk zijn van de trilmodus. + Activeer dit om te forceren dat er geen alarmgeluid wordt afgespeeld, onafhankelijk van andere alarmgeluidsinstellingen. + Startvertraging + Vertraging voordat trilling en geluid voor het eerst worden afgespeeld.\nSoms stopt de melding van de telefoon de trilling onmiddellijk. + Kleur verouderde waarde + Kleur voor geen nieuwe glucosewaarden. + Geen pop-up terwijl telefoon is verbonden + Geen extra Wear meldingspop-up terwijl de telefoon is verbonden.\nDe trilling en het geluid worden geactiveerd zoals geconfigureerd. + Details + Verbindingen + verbroken + verbonden + Kleurenschema van de applicatie + Gebruik systeemschema + Licht schema + Donker schema + Tikactie + Selecteer een actie of applicatie die moet worden uitgevoerd wanneer u op dit item tikt. + Geen actie + Zwevende widget in-/uitschakelen + Tikactie voor complicatie + Schakel deze instelling uit als de trendpijl te groot of bijgesneden is. + Gebruikersinterface + Algemene instellingen voor de gebruikersinterface van deze applicatie. + GlucoDataAuto is actief + Schakel dit alleen in als u een volgerbron en alleen Android Auto-meldingen gebruikt.\nDit voorkomt dat Android deze app sluit terwijl deze niet is verbonden met Android Auto, zodat de app de Android Auto-verbinding niet kan herkennen.\nIn plaats van deze instelling in te schakelen, kunt u de app ook eenmaal openen in Android Auto als u verbonden bent. + Batterijoptimalisatie is ingeschakeld!\nDruk hier om het uit te schakelen. + Als u een volgerbron gebruikt, wordt de app mogelijk niet gestart terwijl deze is verbonden met Android Auto, als u alleen Android Auto-meldingen gebruikt.\nOm ervoor te zorgen dat de app de Android Auto-verbinding detecteert, moet u batterijoptimalisatie uitschakelen.\nAls het nog steeds niet werkt, kunt u ook de voorgrondmodus inschakelen of moet u de app eenmaal openen in Android Auto als deze is verbonden met Android Auto. + Android Auto instellen + Android Auto is een aparte app of onderdeel van het systeem en is toegankelijk via de Android-instellingen.\nOm GlucoDataAuto voor Android Auto te activeren, moet u de volgende stappen uitvoeren: + 1. Ontwikkelaarsmodus activeren + - open Android Auto\n- scroll naar beneden naar de versie\n- tik meerdere keren op de versie totdat er een pop-up verschijnt om \"Ontwikkelaarsinstellingen toestaan\"\n- druk op \"OK\" + 2. \"Onbekende bronnen\" activeren + - open Android Auto\n- open in het 3-puntsmenu de \"Ontwikkelaarsinstellingen\"\n- scroll naar beneden naar \"Onbekende bron\"\n- schakel het in + 3. Meldingsinstellingen instellen + - open Android Auto\n- scroll naar beneden naar \"Berichten\"\n- schakel \"Berichtmeldingen weergeven\" in\n- schakel \"Eerste regel van berichten weergeven\" in + 4. GlucoDataAuto inschakelen + - open Android Auto\n- scroll naar beneden naar \"Weergave\"\n- open \"Launcher aanpassen\"\n- schakel \"GlucoDataAuto\" in\nAls GlucoDataAuto niet beschikbaar is, start u uw telefoon opnieuw op. + Gekleurde lay-out + Geeft de waarden weer in grote en gekleurde tekst.\nOp sommige Samsung-apparaten met Android 13 en hoger kan dit ertoe leiden dat de app crasht vanwege een bug aan de kant van Samsung.\nAls het probleem zich voordoet, wordt deze instelling automatisch uitgeschakeld om ervoor te zorgen dat de app functioneel blijft. + Crash gedetecteerd! + Het spijt me dat de app is gecrasht tijdens de uitvoering!\nStuur me de logboeken zodat ik de fout kan analyseren en zo snel mogelijk kan proberen op te lossen.\nBedankt! + Dexcom Share + Amerikaans account + Amerikaans account wordt gebruikt. + Internationaal account wordt gebruikt. + Dexcom Share-account inschakelen. U moet eerst de gebruikersnaam en het wachtwoord instellen. + Gebruikersnaam + Gebruikersnaam, e-mailadres of telefoonnummer om in te loggen op uw Dexcom Share-account.\nBELANGRIJK: als u een telefoonnummer gebruikt, moet dit de landcode bevatten (bijv. +1 voor de VS). + Wachtwoord voor Dexcom Share-account. + Dexcom Share instellen + Ontbrekende toestemming! + De instelling \'%1$s\' is ingesteld, maar de bijbehorende toestemming is niet verleend! Druk op \"OK\" om door te gaan naar het toestemmingsdialoogvenster of druk op \"Annuleren\" om deze instelling uit te schakelen. + Broadcast Service API + Gebruik xDrip+ Broadcast Service API om gegevens (ook IOB) van xDrip+ te ontvangen.\nActiveer eerst \"Broadcast Service API\" in \"Inter-app-instellingen\" in xDrip+! + Verouderde tijd + Tijd in minuten waarna de waarde als verouderd wordt behandeld.\nNa tweemaal deze tijd wordt de waarde verwijderd. + Lokale applicaties + Applicaties op hetzelfde apparaat die glucosewaarden leveren als intern broadcastbericht, zoals Juggluco, xDrip+, AAPS, enzovoort. + Cloudservices die glucosewaarden leveren via internet, zoals LibreLinkUp, Dexcom Share en Nightscout. + Alternatief: Lokaal uitzenden + Om waarden van Dexcom BYODA te ontvangen, moet u uitzenden naar AAPS inschakelen tijdens het bouwen van de app. + Om waarden van Eversense te ontvangen, moet u ESEL gebruiken in de companion-modus (melding lezen) of verbonden met de gepatchte Eversense-app.\nVoor meer informatie over ESEL, drukt u hier. + Om waarden van Dexcom BYODA te ontvangen, moet u uitzenden naar xDrip+ inschakelen tijdens het bouwen van de app. + Versie %s is beschikbaar + Notities + Meldingen zijn uitgeschakeld! \nDruk hier om ze in te schakelen. + Andere eenheid weergeven + Toon glucosewaarde in %s in de details. + Andere eenheid + Druk hier om uw Dexcom Share-accountgegevens voor uw niet-Amerikaanse account te controleren. + Druk hier om uw Dexcom Share-accountgegevens voor uw Amerikaanse account te controleren. + Wijzig de grootte van de vergrendelschermgegevens. + Verbinding resetten + Druk hier om de verbinding tussen het horloge en de telefoon te resetten als deze niet naar verwachting werkt.\nAls het resetten het probleem niet oplost, start u uw horloge opnieuw op. + De verbinding werkt niet naar verwachting! Probeer uw horloge opnieuw op te starten. + Tijd tot sluiten + Tijd in seconden om de zwevende widget te sluiten terwijl u op de widget drukt zonder deze te verplaatsen.\n0 om deze instelling uit te schakelen. + Stille modus inschakelen + Geen alarmmeldingen voor dit alarm tijdens stille modus.\nConfigureer uw gewenste tijden om deze functie te gebruiken. + Geen alarmmeldingen voor alle alarmen tijdens stille modus.\nConfigureer uw gewenste tijden om deze functie te gebruiken. + Starttijd + Selecteer de tijd waarop u wilt dat uw stille modus begint. + Eindtijd + Selecteer de tijd waarop u wilt dat uw stille modus eindigt. + Dagen voor stille modus + Geef de dagen van de week op waarop stille modus moet beginnen op de geselecteerde starttijd. + + De app crasht vanwege de gekleurde meldingslay-out!\nDeze is uitgeschakeld om deze crash te voorkomen.\nEen telefoon herstart lost dit meestal op. + Melding automatisch sluiten + Sluit de alarmmelding automatisch nadat het geluid en de trilling zijn voltooid, maar pas na minimaal 30 seconden.\nAls opnieuw activeren actief is, wordt de melding gesloten als er geen extra trigger is. + + Instellingen succesvol opgeslagen + Instellingen opslaan mislukt! + Instellingen succesvol gelezen + Instellingen lezen mislukt! + Exporteren / Importeren + Instellingen exporteren of importeren en logboeken opslaan. + Exporteren + Druk hier om alle instellingen naar een bestand te exporteren. \nBELANGRIJK: hiermee worden alleen gewijzigde instellingen geëxporteerd! Instellingen met standaardwaarden maken geen deel uit van de export. + Importeren + Druk hier om instellingen uit een bestand te importeren.\nBELANGRIJK: hiermee worden geen standaardinstellingen opnieuw aangemaakt, omdat deze geen deel uitmaken van de export.\nAls u de standaardwaarden wilt herstellen, verwijdert u eerst de app-gegevens en importeert u daarna. + + Logboeken van de telefoon opslaan naar bestand. + Logboeken van verbonden Wear OS horloge opslaan naar bestand. + Herhalen + Geen trilling + Trilmodus + + Snel stijgend + Snel stijgen alarm + Alarm als de glucosewaarde hoger is dan of gelijk is aan %1$s en de delta met ten minste +%2$s stijgt voor %3$d opeenvolgende metingen. + Snel stijgend! + + Snel dalend + Snel dalen alarm + Alarm als de glucosewaarde lager is dan of gelijk is aan %1$s en de delta met ten minste -%2$s daalt voor %3$d opeenvolgende metingen. + Snel dalend! + Trilintensiteit + Intensiteit van het trillen + Alarminstellingen + Deltawaarde wanneer het alarm moet worden geactiveerd. + Aantal keren voorkomen + Aantal keren dat de deltawaarde achter elkaar moet worden bereikt. + Grens + Grens voor het stijgen/dalen alarm.\nDe glucosewaarde moet hoger/lager zijn om het alarm te activeren. + per minuut + per 5 minuten + Andere alarmen + Geavanceerde alarminstellingen + Interne berichten van deze bron zullen worden verwerkt. + Interne berichten van deze bron zullen worden genegeerd. + Tijdelijk uitgeschakeld + Snoozen tot + Tijdelijk uitgeschakeld tot + + + Trend + %1$d graden omhoog + %1$d graden omlaag + + Alarmen: %1$s + inactief + snoozen tot %1$s + tijdelijk uitgeschakeld tot %1$s + + ingeschakeld + uitgeschakeld + + niet beschikbaar + + Waarden uitspreken + + + Tijd %d minuut + Tijd %d minuten + + Open Integratie en schakel \"Gegevens delen met GWatch\" in\nOpmerking: de DiaBox-uitzending bevat alleen de glucosewaarde en geen verdere informatie zoals trend! + Opmerking: de AAPS-uitzending van BYODA lijkt niet te werken met andere apps. \nGebruik de xDrip+-uitzending in BYODA. + + Aan de slag met GDA op Android Auto + Tekst-naar-spraak (TTS) is niet ingeschakeld.\nOm deze functie te gebruiken, schakelt u TTS in de Android-toegankelijkheidsinstellingen in.\nTik hier om te updaten nadat u deze hebt ingeschakeld. + Waarde uitspreken + Afspelen + Spreek de huidige waarde uit tijdens het afspelen.\nVereist dat Tekst-naar-spraak is ingeschakeld. + Speel een leeg geluid af om de mediaspeler naar de voorgrond van het gesplitste scherm te brengen. + Nieuwe waarde uitspreken + Spreek nieuwe waarden uit in Android Auto, zelfs wanneer u een andere mediaspeler gebruikt. + Tik om tekst-naar-spraak te testen. + Spreek alleen alarmen uit. + Stel het interval in voor het uitspreken van nieuwe waarden in Android Auto. + Het interval voor het ontvangen van nieuwe glucosewaarden wordt gebruikt als de duur voor de voortgangsbalk in de mediaspeler.\nAls u de waarde op 0 instelt, wordt de voortgangsbalk uitgeschakeld. + + Schakelaar voor nieuwe waarde uitspreken + Uitspreken is aan + Uitspreken is uit + Alarmpictogram inschakelen + Schakel het alarmpictogram op het hoofdscherm in om de alarmstatus te wijzigen. + + + + Nieuwe voorwaarden automatisch accepteren + Door dit in te schakelen, accepteert u automatisch nieuwe gebruiksvoorwaarden om continue gegevensontvangst te garanderen. U wordt niet apart op de hoogte gebracht van nieuwe voorwaarden. Als u ze wilt lezen, schakelt u deze instelling uit en raadpleegt u de LibreLinkUp app in geval van fout 4. + GlucoDataHandler kan automatisch nieuwe gebruiksvoorwaarden voor LibreLinkUp accepteren.\nDruk op OK om deze functie in te schakelen of op Annuleren om de voorwaarden in de LibreLinkUp app te lezen en handmatig te accepteren. + + Fout, log opnieuw in op de LibreLinkUp app en controleer op eventuele stappen die moeten worden uitgevoerd! + Fouttype %1$s, log opnieuw in op de LibreLinkUp app en controleer op eventuele stappen die moeten worden uitgevoerd! + Nieuwe gebruiksvoorwaarden moeten worden geaccepteerd. Schakel automatische acceptatie in de LibreLinkUp-broninstellingen in of log opnieuw in op de LibreLinkUp app en accepteer de voorwaarden. + Onjuiste gebruikersnaam of wachtwoord. Controleer uw LibreLinkUp-broninstellingen. Probeer indien nodig opnieuw in te loggen of de LibreLinkUp app opnieuw te installeren en uw accountinstellingen te controleren.\nStop alle andere apps die toegang hebben tot hetzelfde account! + Laatste glucosemeting op server van %1$s + Geen glucosemetingen gevonden op server. + Teveel verzoeken! + Stop alle andere apps die toegang hebben tot hetzelfde account! + + Wachten op gegevens + Batterijniveau verzenden + Verzend batterijniveau naar horloge om weer te geven in complicatie en op hoofdscherm. + Batterijniveau weergeven + Stel de minimale tijd (in minuten) in tussen glucosemetingen voordat een nieuwe update naar het horloge wordt verzonden.\nAlarmen en zeer lage metingen worden altijd verzonden. + Stel de minimale tijd (in minuten) in tussen glucosemetingen voordat een nieuwe update naar de ontvangers wordt verzonden. + Het alarm zal herhaaldelijk klinken en trillen totdat u de melding sluit of de ingestelde maximale alarmduur is bereikt. + Maximale alarmduur + Geef de maximale tijd (in minuten) op dat het alarm zal klinken en trillen. Een waarde van 0 betekent dat het alarm oneindig zal herhalen. + Om bronnen in te schakelen, stelt u ze eerst in de telefoonapp in. Activeer bronnen op uw horloge alleen als u de gegevens nog niet op uw telefoon ontvangt. + Opmerking van de ontwikkelaar + Deze app is in mijn vrije tijd ontwikkeld als hobbyproject. Daarom kan ik niet garanderen dat alle functies feilloos werken op elk apparaat. Ik werk echter constant aan verbeteringen.\nAls u problemen ondervindt of vragen heeft, neem dan rechtstreeks contact met mij op voordat u een negatieve recensie achterlaat. \nOm op de hoogte te blijven van belangrijke updates en bugfixes, bent u welkom om lid te worden van de Facebook-groep of de Google Groups. + + Om gegevens van Dexcom Share-servers te ontvangen:\nIn de Dexcom app:\n - Maak een volger aan\n - Zorg ervoor dat onder \"Verbindingen\" \"Delen aan\" wordt weergegeven + In GlucoDataHandler:\n - Voer uw Dexcom Clarity gebruikersnaam en wachtwoord in \n (als u uw telefoonnummer als gebruikersnaam gebruikt, voeg dan uw landcode toe)\n - Controleer de instelling voor een Amerikaans account\nBelangrijk: het werkt niet met een volgersaccount! + + Bijgewerkt + Alarmen + Wijzerplaten + Informatie over wijzerplaten van derden voor Wear OS. + Instellen + Wijzerplaten van derden + Deze app biedt geen wijzerplaat, maar biedt verschillende complicaties (de kleine ronde onderdelen van de wijzerplaat), die kunnen worden gebruikt voor wijzerplaten die complicaties van derden ondersteunen. + Standaard + De meeste horlogefabrikanten bieden al wijzerplaten die kunnen worden gebruikt met de GlucoDataHandler complicatie.\nDe meeste Samsung \"Info\"-wijzerplaten zijn bijvoorbeeld compatibel. + Pujie + Pujie is een app voor het maken van wijzerplaten met een cloudbibliotheek.\nOm compatibele wijzerplaten te vinden, zoekt u in de bibliotheek naar wijzerplaten die beginnen met \"GDH_\".\nOpmerking: Pujie is niet compatibel met Wear OS 5 apparaten.\nTik hier om de app in de Play Store te openen. + Diabetic Masked Man + Diabetic Masked Man maakt wijzerplaten van derden die speciaal zijn ontworpen voor gebruik met GlucoDataHandler.\nTik hier om zijn Play Store-pagina te openen. + GDC Watch Faces + GDC Watch Faces zijn ontworpen voor gebruik met GlucoDataHandler.\nTik hier om de Play Store-pagina te openen. + Complicaties altijd bijwerken + Stuur altijd nieuwe glucosewaarden naar het horloge.\nSchakel dit uit om te voorkomen dat gegevens op het horloge worden bijgewerkt terwijl het horlogescherm is uitgeschakeld om de batterij te sparen.\nAlarmwaarden worden altijd naar het horloge verzonden.\nBELANGRIJK: het verouderde alarm is uitgeschakeld op het horloge wanneer deze instelling is uitgeschakeld! + Altijd waarschuwen voor zeer laag + Dit alarm zal u waarschuwen, zelfs als snoozen of de algemene stille modus voor alle alarmen actief zijn, zodat u zeker weet dat u wordt gewaarschuwd voor zeer lage glucosewaarden. + + Als deze fout aanhoudt, probeer dan de Libre app opnieuw te installeren. Dit lost het probleem vaak op. + Server + Standaard (.io) + Russisch (.ru) + + Naam + Naam van de gebruiker/patiënt, die moet worden weergegeven in de melding en tijdens het spreken.\nDit moet worden gebruikt als u meerdere varianten van GlucoDataAuto gebruikt, anders kan het leeg zijn. + + Video-tutorial + Klik hier om naar de YouTube-video te gaan die de installatie van deze bron illustreert. Veel dank aan Diabetic Masked Man voor het maken van de video\'s. + + 30 minuten + 60 minuten + 90 minuten + 120 minuten + Snoozen op melding + Kies maximaal 3 sluimertijden om weer te geven als knoppen op de alarmmelding. + + IOB/COB waarden weergeven + IOB en COB waarden weergeven, indien beschikbaar. + + diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index 3a9169315..11a1f88d4 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -380,8 +380,6 @@ Kategorie alarmów Powiadomienie pełnoekranowe Włącza powiadomienie pełnoekranowe na ekranie blokady.\nMoże to wymagać dodatkowych uprawnień. - Przycisk „Odłóż” w powiadomieniu - Jeśli opcja zostanie włączona, powiadomienia będą również zawierać przyciski do odłożenia alarmów. Odrzuć blokadę klawiszy Włącz to ustawienie, jeśli masz problemy z powiadomieniem pełnoekranowym.\nNa niektórych urządzeniach spowoduje to odblokowanie ekranu, gdy uruchomione zostanie powiadomienie pełnoekranowe.\nNie włączaj tego ustawienia, jeśli nie masz żadnych problemów. Wymuś dźwięk alarmu @@ -458,7 +456,7 @@ Przesyłanie wartości Wizualizacja Zegarki BangleJS - Aby korzystać z aplikacji GlucoDataHandler na zegarkach z Wear OS, zainstaluj ją ze Sklepu Play Google.\nTa aplikacja nie zawiera w sobie tarczy zegarka. Dzięki niej można wstawić kilka komplikacji (małych okrągłych wskaźników) na tarcze, które dają taką możliwość. + Aby korzystać z aplikacji GlucoDataHandler na zegarkach z Wear OS, zainstaluj ją ze Sklepu Google Play. Sprawdź połączenie Naciśnij tutaj, aby ponownie sprawdzić połączenie z Wear OS.\nMożesz również uruchomić to sprawdzenie, naciskając informacje o połączeniu na ekranie głównym (nawet w aplikacji Wear OS). WatchDrip+ obsługuje kilka opasek MiBand i zegarków Amazfit.\nAby uzyskać pełną listę obsługiwanych urządzeń i więcej informacji na temat konfiguracji WatchDrip+, naciśnij tutaj. @@ -515,7 +513,6 @@ Nazwa użytkownika, adres e-mail lub numer telefonu do logowania do konta Dexcom Share.\nWAŻNE: numer telefonu musi zawierać prefiks kraju, np. +48 dla Polski. Hasło do konta Dexcom Share. Konfiguracja Dexcom Share - Aby odbierać dane z serwerów Dexcom Share, musisz mieć:\n- włączone udostępnianie danych w aplikacji Dexcom, która jest połączona z sensorem\n- zaakceptowane zaproszenie w aplikacji Dexcom Follower (możesz ją później odinstalować)\n\n Ważne: nie działa z użytkownikiem obserwującym! Brak uprawnienia! Ustawienie \"%1$s\" jest ustawione, ale powiązane uprawnienie nie zostało przyznane! Naciśnij \"OK\", aby wyświetlić okno dialogowe uprawnień lub naciśnij \"Anuluj\", aby wyłączyć to ustawienie. Interfejs API usługi transmisji @@ -558,7 +555,7 @@ Dni godzin ciszy Określ dni tygodnia, w których godziny ciszy powinny rozpoczynać się o wybranej godzinie rozpoczęcia. - Aplikacja zawiesza się z powodu układu kolorowych powiadomień! Został on wyłączony, aby zapobiec tej awarii. Jest to problem dotyczący telefonów Samsung, więc utwórz raport o błędzie w aplikacji Samsung Members. Dziękuję! + Aplikacja zawiesza się z powodu układu kolorowych powiadomień! Został on wyłączony, aby zapobiec tej awarii. Zazwyczaj ponowne uruchomienie telefonu to naprawia. Automatyczne zamknięcie powiadomienia Automatyczne zamknięcie powiadomienia alarmowego po zakończeniu odtwarzania dźwięku i wibracji, ale dopiero po upływie co najmniej 30 sekund.\nJeśli aktywna jest opcja ponownego włączenia, powiadomienie zostanie zamknięte, jeśli nie będzie dodatkowego wyzwalacza alarmu. Ustawienia zapisane pomyślnie @@ -574,7 +571,6 @@ Zapisz logi z telefonu do pliku. Zapisz logi z podłączonego zegarka Wear OS do pliku. Powtórz - "Powtórz dźwięk alarmu i wibracje. \n0: Wyłącza funkcję powtarzania. \nWartości dodatnie: Ustawienie czasu do powtórzenia w minutach. \n-1: Włącza nieograniczone powtarzanie. " Brak wibracji Tryb wibracji @@ -602,6 +598,8 @@ Wewnętrzne wiadomości z tego źródła będą obsługiwane. Wewnętrzne wiadomości z tego źródła będą ignorowane. Tymczasowo wyłączony + Drzemka do + Tymczasowo wyłączone do Trend @@ -646,7 +644,77 @@ Wł./Wył. odczytywania nowej wartości Funkcja odczytywania jest włączona Funkcja odczytywania jest wyłączona - Włącz ikonę alarmów - Pokaż na ekranie głównym ikonę alarmów, która umożliwia włączanie i wyłączanie alarmów. + Aktywuj ikonę alarmów + Aktywuj na ekranie głównym ikonę alarmów, która umożliwia ich włączanie i wyłączanie. + + + + Błąd, zaloguj się ponownie do aplikacji LibreLinkUp i sprawdź, jakie czynności należy wykonać! + Błąd typu %1$s, zaloguj się ponownie do aplikacji LibreLinkUp i sprawdź, czy są jakieś kroki do wykonania! + Automatyczna akceptacja nowych warunków + Włączenie tej opcji oznacza automatyczną akceptację nowych Warunków użytkowania w celu zapewnienia ciągłości odbioru danych. Nie będziesz za każdym razem informowany o nowych warunkach. Jeśli chcesz się z nimi zapoznać, wyłącz to ustawienie i przejdź do aplikacji LibreLinkUp w przypadku błędu 4. + Należy zaakceptować nowe Warunki użytkowania. Włącz automatyczną akceptację w ustawieniach źródła LibreLinkUp lub zaloguj się ponownie do aplikacji LibreLinkUp i zaakceptuj warunki. + GlucoDataHandler może automatycznie akceptować nowe warunki użytkowania LibreLinkUp.\nNaciśnij OK, aby włączyć tę funkcję, lub Anuluj, aby przeczytać i ręcznie zaakceptować warunki w aplikacji LibreLinkUp. + Nieprawidłowa nazwa użytkownika lub hasło. Sprawdź ustawienia źródła LibreLinkUp. W razie potrzeby spróbuj zalogować się ponownie lub ponownie zainstaluj aplikację LibreLinkUp i sprawdź ustawienia konta.\nZatrzymaj wszystkie inne aplikacje uzyskujące dostęp do tego samego konta! + Ostatni odczyt glukozy na serwerze sprzed %1$s + Na serwerze nie znaleziono odczytów poziomu glukozy. + Zbyt wiele żądań! + Zatrzymaj wszystkie inne aplikacje uzyskujące dostęp do tego samego konta! + Oczekiwanie na dane + Prześlij poziom baterii + Prześlij informację o poziomie naładowania baterii do zegarka, aby wyświetlić ją w komplikacjach i na ekranie głównym. + Pokaż poziom baterii + Ustaw minimalny czas (w minutach) między pomiarami poziomu glukozy przed wysłaniem nowej aktualizacji do zegarka.\nAlarmy i wartości bardzo niskie będą zawsze wysyłane. + Ustaw minimalny czas (w minutach) między pomiarami poziomu glukozy przed wysłaniem nowej aktualizacji do odbiorców. + Alarm będzie emitował dźwięk i wibracje do momentu odrzucenia powiadomienia lub osiągnięcia ustawionego maksymalnego czasu trwania alarmu. + Maksymalny czas trwania alarmu + Określ maksymalny czas (w minutach), przez jaki alarm będzie emitował dźwięk i wibracje. Wartość 0 oznacza, że alarm będzie powtarzany w nieskończoność. + Aby włączyć źródła, należy je najpierw skonfigurować w aplikacji telefonu. Aktywuj źródła na zegarku tylko wtedy, gdy nie otrzymujesz jeszcze danych na telefonie. + Uwaga od dewelopera + Aplikację tę stworzyłem w wolnym czasie i jest to mój projekt hobbystyczny. W związku z tym nie mogę zagwarantować, że wszystkie funkcje będą działać bezbłędnie na każdym urządzeniu. Jeśli napotkasz jakiekolwiek problemy lub masz pytania, skontaktuj się ze mną bezpośrednio przed pozostawieniem negatywnej recenzji. \nAby być na bieżąco z ważnymi aktualizacjami i poprawkami błędów, zapraszam do dołączenia do grupy na Facebooku lub Grup Google. + Wartość odebrano + Alarmy + + Aby odbierać dane z serwerów Dexcom Share:\nW aplikacji Dexcom:\n - Utwórz obserwatora\n - W sekcji "Połączenia" upewnij się, że wyświetlana jest opcja "Udostępniaj". + W GlucoDataHandler:\n - Wprowadź nazwę użytkownika i hasło Dexcom Clarity \n (jeśli używasz numeru telefonu jako nazwy użytkownika, dołącz kod kraju) \n - Sprawdź ustawienie konta US\nWażna uwaga: Opcja ta nie działa z kontem obserwatora! + + Tarcze zegarków + Informacje o oddzielnych tarczach zegarków dla Wear OS. + Konfiguracja + Oddzielne tarcze zegarków + Ta aplikacja nie zawiera w sobie tarczy zegarka. Dzięki niej można wstawić kilka komplikacji (małych okrągłych wskaźników) na tarcze, które dają możliwość wstawiania ich z innych aplikacji. + Standardowe + Większość producentów zegarków dostarcza już tarcze zegarków, które mogą być używane z komplikacją z GlucoDataHandler. Na przykład większość tarcz zegarków Samsung \"Info"\ daje taką możliwość. + Pujie + Pujie to aplikacja do tworzenia tarcz zegarka z biblioteką chmurową.\nAby znaleźć kompatybilne tarcze zegarka, wyszukaj w bibliotece tarcze zegarka zaczynające się od \"GDH_\".\nUwaga: Aplikacja Pujie nie jest kompatybilna z urządzeniami z systemem Wear OS 5.\nStuknij tutaj, aby otworzyć aplikację w Sklepie Play. + Diabetic Masked Man + Diabetic Masked Man tworzy oddzielne tarcze zegarków zaprojektowane specjalnie do użytku z GlucoDataHandler.\nStuknij tutaj, aby otworzyć jego stronę w Sklepie Play. + Tarcze zegarka GDC + Tarcze zegarka GDC są przeznaczone do użytku z aplikacją GlucoDataHandler.\nStuknij tutaj, aby otworzyć stronę Sklepu Play. + Zawsze aktualizuj komplikacje + Zawsze wysyłaj nowe wartości glukozy do zegarka.\nWyłącz tę opcję, aby zapobiec aktualizowaniu danych na zegarku, gdy ekran zegarka jest wyłączony w celu oszczędzania baterii.\nWartości alarmowe będą zawsze przesyłane do zegarka.\nWAŻNE: Gdy opcja ta jest włączona, alarm wartości nieaktualnych jest wyłączony w zegarku! + Zawsze alarm dla wart. bardzo niskich + Ten alarm powiadomi Cię nawet wtedy, gdy aktywna jest drzemka lub trwają ogólne ciche godziny dla wszystkich alarmów, gwarantując, że zostaniesz powiadomiony o bardzo niskim poziomie glukozy. + + Jeśli ten błąd nadal występuje, spróbuj ponownie zainstalować aplikację LibreLink. To często rozwiązuje problem. + Serwer + Domyślny (.io) + Rosyjski (.ru) + + Nazwa + Nazwa użytkownika/pacjenta, która ma być widoczna w powiadomieniach i w odczytach na głos.\nZ nazwy tej należy korzystać, jeżeli używa się wielu wariantów GlucoDataAuto, ponieważ w przeciwnym razie pole to może być puste. + + Przewodnik wideo + Naciśnij tutaj, by przejść do filmu na kanale YouTube, który pokazuje, jak ustawić to źródło. Wielkie dzięki dla Diabetic Masked Mana za przygotowanie tych filmów. + + 30 minut + 60 minut + 90 minut + 120 minut + Przycisk „Odłóż” w powiadomieniu + Wybierz do 3 długości, na jakie można odłożyć alarm, które będą wyświetlane jako przyciski na powiadomieniu o alarmie. + + Pokaż wartości IOB/COB + Pokaż wartości IOB i COB, jeśli są dostępne. diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index 6ea98f945..fc30e695d 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -306,7 +306,7 @@ - habilite \"Broadcast Compatível\"\n- marque \"Identificar receptores\" preencha com \"de.michelinside.glucodatahandler\" (sem aspas), use espaço como separador caso já tenha algo preenchido - habilite \"Broadcast Compatível\"\n- marque \"Identificar receptores\" preencha com \"de.michelinside.glucodataauto\" (sem aspas), use espaço como separador caso já tenha algo preenchido Configurar LibreLinkUp - IMPORTANTE: esta não é a conta LibreView!\nPara ativar o LibreLinkUp:\n- abra seu aplicativo FreeStyle Libre e selecione no menu Compartilhar ou Aplicativos conectados\n- ative a conexão LibreLinkUp\n- instale o LibreLinkUp da PlayStore\n- configure seu conta e aguarde o convite + IMPORTANTE: esta não é a conta LibreView!\nPara ativar o LibreLinkUp:\n- abra seu aplicativo FreeStyle Libre e selecione no menu Compartilhar ou Aplicativos conectados\n- ative a conexão LibreLinkUp\n- instale o LibreLinkUp da Play Store\n- configure seu conta e aguarde o convite Contato IOB/COB support Receba também valores IOB e COB do endpoint de seixo Nightscout. @@ -383,8 +383,6 @@ Categoria de alarme Notificação em tela cheia Ativa notificação em tela cheia na tela de bloqueio.\nPermissão adicional pode ser requerida. - Adiar a notificação - Se ativado, as notificações também incluirão botões para ativar o adiamento do alarme. Dispensar keyguard Ative esta configuração se tiver problemas com a notificação de tela cheia.\nEm alguns dispositivos, isso fará com que a tela seja desbloqueada, enquanto a notificação de tela cheia é acionada.\nPortanto, não ative-a se não tiver problemas. Forçar som de alarme @@ -461,7 +459,7 @@ Transferir valores Visualização Relógios BangleJS - Para usar GlucoDataHandler em relógios Wear OS, instale-o via Google Playstore.\nEste aplicativo não fornece um mostrador de relógio, mas fornece várias complicações (as pequenas partes redondas do mostrador do relógio), que podem ser usadas em mostradores de relógio que suportam complicações de terceiros. + Para usar GlucoDataHandler em relógios Wear OS, instale-o via Google Play Store. Verifique a conexão Pressione aqui para verificar novamente a conexão com o Wear OS.\nVocê também pode acionar essa verificação pressionando as informações de conexão na tela inicial (mesmo no app Wear OS). WatchDrip+ suporta vários dispositivos MiBand e Amazfit.\nPara obter uma lista completa de dispositivos suportados e mais informações sobre como configurar o WatchDrip+, clique aqui. @@ -518,7 +516,6 @@ Nome de utilizador, e-mail ou número de telefone para iniciar sessão na conta Dexcom Share.\nIMPORTANTE: o número de telefone deve incluir o código do país, por exemplo, +351 para Portugal. Senha para a conta Dexcom Share. Configurar Dexcom Share - Para receber dados dos servidores Dexcom Share, você precisa ter:\n- compartilhamento habilitado no aplicativo Dexcom que está conectado ao sensor\n- aceita a solicitação no aplicativo Dexcom Follower (você pode desinstalá-lo depois)\n\nImportante: não funciona com um usuário seguidor! Missing permission! A configuração \'%1$s\' está ativada, mas a permissão relacionada não foi concedida! \nPressione \"OK\" para ser direcionado ao diálogo de permissões ou pressione \"Cancelar\" para desativar esta configuração. Broadcast Service API @@ -558,7 +555,7 @@ Selecione a hora em que você deseja que suas horas de silêncio terminem. Dias para horas de silêncio Especifique os dias da semana em que as horas de silêncio devem começar na hora de início selecionada. - O aplicativo trava devido ao layout de notificação colorida!\nEle foi desativado para evitar essa falha.\nEste é um problema em telefones Samsung, então crie um relatório de erro no aplicativo Samsung Members. Obrigado! + O aplicativo trava devido ao layout de notificação colorida!\nEle foi desativado para evitar essa falha.\nUm reinício do telefone geralmente resolve isso. Fechar notificação automaticamente Fechar automaticamente a notificação de alarme após o som e a vibração terem terminado, mas somente após pelo menos 30 segundos.\nSe a reativação estiver ativa, a notificação será fechada se não houver outro gatilho. Configurações salvas com sucesso @@ -575,7 +572,6 @@ Salvar logs do telefone em um arquivo. Salvar logs do relógio Wear OS conectado em um arquivo. Repetir - Repetir o som e a vibração do alarme. \n0: Desativa a funcionalidade de repetição. \nValores positivos: Define o intervalo de repetição em minutos. \n-1: Ativa a repetição ilimitada. Sem vibração Modo de vibração Aumento rápido @@ -601,6 +597,8 @@ Mensagens internas desta fonte serão tratadas. Mensagens internas desta fonte serão ignoradas. Desativado temporariamente + Em soneca até %1$s + Desativados temporariamente até %1$s Tendência @@ -641,4 +639,76 @@ Falar está desligado Ativar ícone de alarme Ativar ícone de alarme na tela principal para alternar o estado do alarme. + + + + Erro, por favor, faça login novamente no aplicativo LibreLinkUp e verifique se há alguma etapa a ser realizada! + Tipo de erro %1$s, por favor, faça login novamente no aplicativo LibreLinkUp e verifique se há alguma etapa a ser realizada! + Aceitar automaticamente novos termos + Ao habilitar isso, você aceita automaticamente os novos Termos de Uso para garantir a recepção contínua de dados. Você não será informado sobre novos termos separadamente. Se desejar lê-los, desabilite esta configuração e consulte o aplicativo LibreLinkUp em caso de erro 4. + Novos Termos de Uso devem ser aceitos. Habilite a aceitação automática nas configurações de origem do LibreLinkUp ou faça login novamente no aplicativo LibreLinkUp e aceite os termos. + GlucoDataHandler pode aceitar automaticamente os novos Termos de Uso para LibreLinkUp.\nPressione OK para habilitar este recurso ou Cancelar para ler e aceitar manualmente os termos no aplicativo LibreLinkUp. + Nome de usuário ou senha incorretos. Por favor, verifique suas configurações de origem do LibreLinkUp. Se necessário, tente fazer login novamente ou reinstalar o aplicativo LibreLinkUp e verificar as configurações da sua conta.\nPor favor, pare todos os outros aplicativos que estão acessando a mesma conta! + Última leitura de glicose no servidor de %1$s + Nenhuma leitura de glicose encontrada no servidor. + Muitas solicitações! + Por favor, pare todos os outros aplicativos que estão acessando a mesma conta! + Aguardando dados + Enviar nível da bateria + Enviar nível da bateria para o relógio para mostrar na complicação e na tela principal. + Mostrar nível da bateria + Defina o tempo mínimo (em minutos) entre as medições de glicose antes de enviar uma nova atualização para o relógio.\nAlarmes e medições muito baixas sempre serão enviados. + Defina o tempo mínimo (em minutos) entre as medições de glicose antes de enviar uma nova atualização para os receptores. + O alarme tocará e vibrará repetidamente até que você descarte a notificação ou a duração máxima definida do alarme seja atingida. + Duração máxima do alarme + Especifique o tempo máximo (em minutos) que o alarme tocará e vibrará. Um valor de 0 significa que o alarme será repetido indefinidamente. + Para habilitar as fontes, configure-as primeiro no aplicativo do telefone. Ative as fontes no seu relógio somente se você ainda não receber os dados no seu telefone. + Nota do Desenvolvedor + Este aplicativo foi desenvolvido no meu tempo livre como um projeto de hobby. Portanto, não posso garantir que todos os recursos funcionarão perfeitamente em todos os dispositivos. No entanto, estou trabalhando constantemente em melhorias.\nSe você encontrar algum problema ou tiver alguma dúvida, entre em contato comigo diretamente antes de deixar uma avaliação negativa. \nPara se manter informado sobre atualizações importantes e correções de bugs, você pode participar do grupo do Facebook ou dos Grupos do Google. + + Para receber dados dos servidores Dexcom Share:\nNo aplicativo Dexcom:\n - Crie um seguidor\n - Em "Conexões", verifique se "Compartilhar" está exibido + No GlucoDataHandler:\n - Insira seu nome de usuário e senha do Dexcom Clarity\n (se você usar seu número de telefone como nome de usuário, inclua o código do país)\n - Verifique a configuração para uma conta dos EUA\nImportante: isso não funciona com uma conta de seguidor! + + Atualizado + Alarmes + + Mostradores de relógio + Informações sobre mostradores de terceiros para Wear OS. + Configuração + Mostradores de terceiros + Este aplicativo não fornece um mostrador, mas fornece várias complicações (as pequenas partes redondas do mostrador), que podem ser usadas para mostradores que suportam complicações de terceiros. + Padrão + A maioria dos fabricantes de relógios já fornece mostradores que podem ser usados com a complicação GlucoDataHandler.\nPor exemplo, a maioria dos mostradores "Info" da Samsung são compatíveis. + Pujie + Pujie é um aplicativo criador de mostradores com uma biblioteca na nuvem.\nPara encontrar mostradores compatíveis, pesquise na biblioteca por mostradores que começam com "GDH_".\nObservação: Pujie não é compatível com dispositivos Wear OS 5.\nToque aqui para abrir o aplicativo na Play Store. + Diabetic Masked Man + Diabetic Masked Man cria mostradores de terceiros projetados especificamente para uso com GlucoDataHandler.\nToque aqui para abrir sua página da Play Store. + Mostradores GDC + Os mostradores GDC são projetados para uso com GlucoDataHandler.\nToque aqui para abrir a página da Play Store. + Atualizar sempre as complicações + Enviar sempre novos valores de glicose para o relógio.\nDesative esta opção para evitar atualizações de dados no relógio enquanto a tela do relógio estiver desligada para economizar bateria.\nOs valores de alarme sempre serão enviados para o relógio.\nIMPORTANTE: O alarme obsoleto é desativado no relógio quando esta configuração está desativada! + Alertar sempre para valores muito baixos + Este alarme irá notificá-lo mesmo que a função soneca ou o horário de silêncio geral para todos os alarmes estejam ativos, garantindo que você seja alertado sobre níveis de glicose muito baixos. + + Se este erro persistir, tente reinstalar o aplicativo LibreLink. Isso geralmente resolve o problema. + Servidor + Padrão (.io) + Russo (.ru) + + Nome + Nome do usuário/paciente, que deve ser mostrado na notificação e durante a fala.\nIsso deve ser usado se você estiver usando várias variantes do GlucoDataAuto, caso contrário, poderá estar vazio. + + Vídeo tutorial + Clique aqui para ir ao vídeo do YouTube que ilustra a configuração desta fonte. Muito obrigado ao Diabetic Masked Man por criar os vídeos. + + 30 minutos + 60 minutos + 90 minutos + 120 minutos + Adiar a notificação + Escolha até 3 durações de soneca para exibir como botões na notificação de alarme. + + Mostrar valores de IOB/COB + Mostrar valores de IOB e COB, se disponíveis. + diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml new file mode 100644 index 000000000..8cf734e19 --- /dev/null +++ b/common/src/main/res/values-ru/strings.xml @@ -0,0 +1,712 @@ + + + ru + + мг/дл + ммоль/л + + Датчик + Значение + Необработанное + Дельта + Скорость + Временная метка + Время IOB/COB + Разница во времени + Тревога: + Источник + в минуту + Данные еще не получены!\nСначала необходимо выбрать и настроить источник. + + %1$d мин. + \> 1 ч + + очень быстро растет + быстро растет + растет + стабильно + падает + быстро падает + очень быстро падает + + ОК + Отмена + + - + очень низкий + низкий + высокий + очень высокий + + Рекомендуется отключить оптимизацию батареи. + Источник: не активен + Wear OS: подключено (%1$s) + Wear OS: отключено + Android Auto: подключено + Android Auto: отключено + Специальные возможности с высокой контрастностью активны!\nНажмите здесь, чтобы перейти к настройкам. + Новые значения не приходят больше %1$d минут! + + Настройки + Источники данных + GitHub + Обновления + Поддержка + Помощь + + + + Новое значение глюкозы + Устаревшее значение + Сигнал тревоги глюкозы + Состояние подключения Wear OS + Состояние подключения Android Auto + Изменение целевого диапазона глюкозы + + + + Телефон + Wear OS + + Не активен + В процессе + ОК + Не приходят данные + Нет интернет-соединения + Ошибка + + Интервал + Интервал запроса данных из источника. + Задержка + Задержка в секундах перед запросом данных из облака, потому что требуется некоторое время, пока данные там появятся. + + LibreLinkUp + Включить + Включить подписчика LibreLinkUp, если указаны имя пользователя и пароль. + Электронная почта + Электронная почта для входа в LibreLinkUp + Пароль + Пароль для LibreLinkUp + Переподключиться + При следующем интервале будет выполнен новый вход в систему. + + Nightscout + Включить + Включить получение данных из Nightscout. + URL-адрес для Nightscout + URL-адрес сервера Nightscout. + Пароль для Nightscout (API) + Пароль для доступа к данным (при наличии). + Токен доступа + Добавить токен доступа, если он используется (необязательно). + + Всегда + 1 минута + 5 минут + 10 минут + 15 минут + 20 минут + 30 минут + Только сигнал тревоги + + + отключено + не подключено + подключено + ошибка: %s + + + + + Общие + Уведомления + Виджеты + Android Auto + Передача значений глюкозы + Целевые диапазоны + Цвета + + Фиктивные значения + Очень высокая глюкоза + Граница для очень высоких значений глюкозы. + Высокая глюкоза + Граница для высоких значений глюкозы. + Верхняя граница целевого диапазона + Верхнее значение для целевого диапазона. + Нижняя граница целевого диапазона + Нижнее значение для целевого диапазона. + Очень низкая глюкоза + Граница для очень низких значений глюкозы. + Низкая глюкоза + Граница для низких значений глюкозы. + Уведомления Android Auto + Уведомления для Android Auto. + Интервал уведомлений + Минимальный интервал для отображения уведомлений о новом значении.\nСигналы тревоги всегда будут отображать уведомление. + Только сигнал тревоги + Будут запускаться только уведомления о тревогах в их определенном интервале.\nОчень низкие значения всегда будут отображать уведомление. + Фиктивный медиаплеер + Отображать значение глюкозы как текущую песню для Android Auto. + Отправить широковещательное сообщение с данными о глюкозе + Отправить широковещательное сообщение с данными о глюкозе для новых значений, полученных из любого источника. + Определить получателей данных о глюкозе + Выберите получателей, которым будет отправляться широковещательное сообщение с данными о глюкозе. Глобальное широковещательное сообщение будет отправлено каждому приложению, динамически зарегистрированному на широковещательное сообщение. + Отправить исправленное широковещательное сообщение Libre + Переслать значения глюкозы в xDrip+. Выберите \'Libre (исправленное приложение)\' в качестве источника в xDrip+. + Определить получателей исправленного широковещательного сообщения Libre + Выберите получателей, которым будет отправляться исправленное широковещательное сообщение Libre. Глобальное широковещательное сообщение будет отправлено каждому приложению, динамически зарегистрированному на широковещательное сообщение. + Отправить широковещательное сообщение xDrip+ + Отправить широковещательное сообщение, как это делает xDrip+. Например, в AAPS. + Определить получателей широковещательного сообщения xDrip+ + Выберите получателей, которым будет отправляться широковещательное сообщение xDrip+. Глобальное широковещательное сообщение будет отправлено каждому приложению, динамически зарегистрированному на широковещательное сообщение. + Использовать ммоль/л + Использовать ммоль/л вместо мг/дл. + 5-минутный интервал анализа + Если активировано, разность показаний будет анализироваться c 5-минутным интервалом, в противном случае будет использоваться 1-минутный интервал. + Цвет для очень высокого/низкого значения + Цвет для очень высоких и очень низких значений глюкозы. + Цвет для высокого/низкого значения + Цвет для высоких и низких значений глюкозы. + Цвет для целевого диапазона + Цвет для значений глюкозы в целевом диапазоне. + Уведомление + Показывает постоянное уведомление с текущей стрелкой тренда, значением глюкозы и дельты в строке состояния.\nЭто уведомление предотвращает остановку приложения Android. + Значок в строке состояния + Стиль маленького значка уведомления в строке состояния. + Большой значок в строке состояния + Большой значок в строке состояния. Отключите эту настройку, если значок слишком большой и обрезается. + Скрыть содержимое + Это удалит содержимое уведомления, чтобы остался только маленький значок с соответствующим значком. + 2-е уведомление + Показать уведомление без содержимого, чтобы иметь дополнительный значок в строке состояния. + Значок 2-го уведомления в строке состояния + Стиль маленького значка в строке состояния уведомления. + 3-е уведомление + Значок 3-го уведомления в строке состояния + Плавающий виджет + Показывать глюкозу, тренд, дельту, время и IOB/COB в виде плавающего виджета.\nЕсли вы удерживаете и не перемещаете виджет более 5 секунд, он исчезнет. + Размер + Изменить размер плавающего виджета + Стиль + Стиль плавающего виджета. + Прозрачность + Прозрачность фона для плавающего виджета. + Прозрачность виджета + Прозрачность фона для виджетов. + Относительное время + Использовать относительное время вместо временной метки глюкозы для виджетов.\nОтносительное время зависит от настроек батареи на вашем устройстве и может работать некорректно. + + Значок приложения + Значение глюкозы + Стрелка тренда + Дельта + + Wear OS: вибрация + + Глобальная трансляция + + Показать все + Приемник не найден! + + Глюкоза + Глюкоза и тренд + Глюкоза, тренд и дельта + Глюкоза, тренд, дельта и временная метка + Глюкоза, тренд, дельта, время и IOB/COB + + Поддержка BangleJS + Переслать значения в виджет widbgjs для BangleJS. + + + + Получение данных для усложнений + + Телефон: подключен (%1$s) + Телефон: отключен + + На переднем плане + Большая стрелка тренда + Цветной AOD + + + + Глюкоза + Глюкоза (очень большая и цветная) + Глюкоза (цветная) + Глюкоза (большая) + Глюкоза со значком + Глюкоза и тренд + Глюкоза и тренд (большая и цветная) + Глюкоза и тренд (большая) + Глюкоза и тренд (цветная) + Глюкоза и диапазон тренда (если поддерживается) + Глюкоза, дельта и диапазон тренда (если поддерживается) + Дельта, стрелка тренда и диапазон тренда (если поддерживается) + Глюкоза и дельта + Глюкоза, дельта и тренд + Глюкоза, дельта, тренд и временная метка + Глюкоза, значок тренда, дельта и временная метка + Глюкоза и значение тренда + Дельта (большая) + Дельта (большая, цветная) + Дельта + Дельта и тренд + Дельта со значком + Дельта и временная метка + Дельта и временная метка (цветная) + Дельта и временная метка (большая) + Тренд (большой, цветной) + Тренд (цветной) + Значок тренда + Усложнение тестового диапазона с отрицательными значениями (для поддержки диапазона тренда) + Значение тренда + Значение тренда и стрелка + Значение тренда со значком + Временная метка глюкозы + Временная метка + Временная метка (цветная) + Временная метка (большая) + Уровень заряда батареи (часы и телефон) + Уровень заряда батареи часов + Уровень заряда батареи телефона + IOB/COB + + + + https://github.com/pachi81/GlucoDataAuto/blob/main/README.md + Для использования этого приложения в Android Auto необходимо активировать настройки разработчика в Android Auto, несколько раз нажав на версию. Затем необходимо включить \"Неизвестные источники\" в меню настроек разработчика. + Информация + Если вы используете GlucoDataHandler в качестве источника, эти настройки будут перезаписаны из GlucoDataHandler, поэтому вам не нужно менять их здесь. + Отсутствует GlucoDataAuto + Чтобы использовать GlucoDataHandler для Android Auto, необходимо установить GlucoDataAuto с GitHub.\nНажмите здесь для получения дополнительной информации и загрузки GlucoDataAuto. + Переслать в GlucoDataAuto + Отправлять значения глюкозы и настройки в GlucoDataAuto, если телефон подключен к Android Auto. + + + + + Уведомление на переднем плане + Уведомление на переднем плане, которое предотвращает остановку приложения во время работы в фоновом режиме. + Второе уведомление + Третье уведомление + Дополнительное уведомление для отображения дополнительного значка в строке состояния. + Уведомление воркера + Уведомление воркера, отображаемое во время выполнения задач в фоновом режиме. + Уведомление на переднем плане + Уведомление на переднем плане, которое предотвращает остановку приложения во время работы в фоновом режиме. + Уведомление для Android Auto + Уведомление, которое отображается в Android Auto + Блокировка шума активна в настройках взаимодействия приложений xDrip+!\nПожалуйста, измените ее на \"Очень шумный\"! + Облачные сервисы + Juggluco + Для получения значений из Juggluco:\n- откройте Juggluco\n- перейдите в \"Настройки\"\n- включите \"Трансляция Glucodata\"\n- включите \"de.michelinside.glucodatahandler\" + Для получения значений из Juggluco:\n- откройте Juggluco\n- перейдите в \"Настройки\"\n- включите \"Трансляция Glucodata\"\n- включите \"de.michelinside.glucodataauto\" + xDrip+ + Для получения значений из xDrip+:\n- откройте xDrip+\n- перейдите в настройки\n- перейдите в настройки взаимодействия приложений\n- включите \"Локальная трансляция\"\n- установите \"Блокировка шума\" на \"Отправлять даже очень шумные сигналы\" + - включите \"Совместимая трансляция\" - проверьте, чтобы поле \"Идентифицировать получателя\" было пустым или, если запись уже существует, добавьте \"de.michelinside.glucodatahandler\", разделенный пробелом + - включите \"Совместимая трансляция\"\n- проверьте, чтобы поле \"Идентифицировать получателей\" было пустым или добавьте новую строку с \"de.michelinside.glucodataauto\" + Настройка LibreLinkUp + ВАЖНО: это не учетная запись, используемая для приложения FreeStyle Libre!\nЧтобы активировать подписчика LibreLinkUp:\n- откройте приложение FreeStyle Libre и выберите в меню \"Поделиться\" или \"Подключенные приложения\"\n- активируйте подключение LibreLinkUp\n- установите LibreLinkUp из Play Store\n- настройте свою учетную запись и дождитесь приглашения + Связаться + Поддержка IOB/COB + Получать также значения IOB и COB из конечной точки Nightscout Pebble. + Уведомление необходимо для нормальной работы приложенияя.\nНачиная с Android 13, вы можете смахнуть это уведомление. Если вы вообще не хотите его видеть, установите \"Значок в строке состояния\" на \"Значок приложения\" и включите \"Скрыть содержимое\". + AAPS + AndroidAPS + Для получения значений из AAPS:\n- откройте приложение AAPS\n- перейдите в \"Конструктор конфигурации\"\n- включите \"Samsung Tizen\" или \"Транслятор данных\" + WatchDrip+ + \"Включить связь WatchDrip+ (без графика).\nВАЖНО: включите \"Включить службу\" в WatchDrip+ после включения этой настройки здесь!\" + Повторное появление + Интервал, через который уведомление должно появляться снова, если нет нового значения. (0 для никогда). + Стиль значка/изображения фиктивного медиаплеера. + Стиль значка/изображения + Устройство + Wear OS + Сохранить логи + Логи успешно сохранены + Ошибка при сохранении логов! + Логи с Wear OS успешно сохранены + Ошибка при сохранении логов с Wear OS! + Для всех работ, связанных со временем/интервалом, этому приложению требуется разрешение на будильники и напоминания.\nОно не будет добавлять или изменять какие-либо пользовательские напоминания, это только для внутреннего планирования.\nЕсли вы нажмете OK, вы перейдете к настройке разрешений, чтобы включить его для GlucoDataHandler. + Разрешение на будильники и напоминания + Планирование точных будильников отключено!!!\nGlucoDataHandler может работать некорректно!!!\nНажмите здесь, чтобы перейти непосредственно к настройке разрешений. + + Пациент + Если к учетной записи Libre подключено более одного пациента, выберите пациента, для которого будут получаться данные. + Обои экрана блокировки + Включить + Если вы включите обои экрана блокировки, приложение заменит обои на экране блокировки. + Вертикальное положение + Вертикальное положение изображения тренда глюкозы на экране блокировки:\n0 - верх дисплея\n100 - низ дисплея + Отображение уведомлений отключено!!!\nGlucoDataHandler может работать некорректно и не сможет показывать уведомления!!!\nНажмите здесь, чтобы перейти непосредственно к настройке разрешений. + + Нет данных! Сначала примите приглашение в приложении LibreLinkUp! + Цветной значок в строке состояния + Если ваше устройство поддерживает цветные значки в строке состояния, здесь их можно отключить, чтобы использовать белый значок. + + Сигналы тревоги + Интервал + Минимальный интервал в минутах между уведомлениями для этого типа сигнала тревоги.\nИнформация: новое уведомление появится только по истечении времени, если дельта отрицательная. + Минимальный интервал в минутах между уведомлениями для этого типа сигнала тревоги.\nИнформация: новое уведомление появится только по истечении времени, если дельта положительная. + Время в минутах с момента получения последнего значения глюкозы для срабатывания этого уведомления. Оно также будет появляться снова через этот интервал. + Включено + Уведомления включены для этого типа сигнала тревоги. + Уведомления отключены для этого типа сигнала тревоги. + Протестировать + Нажмите здесь, чтобы через 3 секунды сработало уведомление для этого типа сигнала тревоги. + Настройки + Нажмите здесь, чтобы перейти к настройкам канала уведомлений для этого сигнала тревоги.\nТам вы можете изменить все связанные настройки. + Сохранить звук + Нажмите здесь, если хотите сохранить звук сигнала тревоги по умолчанию в файл на телефоне. + Сигнал тревоги сохранен! + Отложить + Все сигналы тревоги + Уведомления о тревоге + Wear OS: Уведомления о тревоге + Уведомления о тревоге активированы.\nПримечание: сигналы тревоги используют громкость будильника телефона. + Уведомления о тревоге отключены. + Остановить + Очень низкий сахар! + Низкий сахар! + Высокий сахар! + Очень высокий сахар! + Тревога: очень низкий уровень + Уведомление об очень низком уровне. + Тревога: низкий уровень + Уведомление о низком уровне. + Тревога: высокий уровень + Уведомление о высоком уровне. + Тревога: очень высокий уровень + Уведомление об очень высоком уровне. + Отключить + Тестовая тревога! + Категории тревоги + Полноэкранное уведомление + Включает полноэкранное уведомление на экране блокировки.\nДля этого может потребоваться дополнительное разрешение. + Отключить защиту клавиатуры + Включите этот параметр, если у вас возникли проблемы с полноэкранным уведомлением.\nНа некоторых устройствах это приведет к разблокировке экрана при срабатывании полноэкранного уведомления.\nПоэтому не включайте его, если у вас нет проблем. + Принудительный звук тревоги + Это приведет к принудительному воспроизведению звука тревоги и вибрации, даже если телефон находится в бесшумном режиме или режиме вибрации. Это всегда будет отключать режим \"Не беспокоить\"!\nЧтобы включить этот параметр, вам может быть предложено разрешить этому приложению изменять режим \"Не беспокоить\". + Wear OS: Принудительный звук тревоги + Принудительная вибрация + Принудительная вибрация, даже если телефон находится в беззвучном режиме. Это всегда будет отключать режим \"Не беспокоить\"!\nЧтобы включить этот параметр, вам может быть предложено разрешить этому приложению изменять режим \"Не беспокоить\". + Уровень звука + Принудительная установка уровня звука для звука тревоги.\n-1 для использования настроек телефона по умолчанию. + Дополнительно + Дополнительные настройки тревоги для полноэкранных уведомлений и звуков. + Уведомление + Звук + Настройки тревоги для значений глюкозы, начиная с %1$s. + Без тревоги при подключ. часах + Уведомления о тревоге не будут отображаться на телефоне, если телефон подключен к GlucoDataHandler на часах. + Уведомления о тревоге будут отображаться на телефоне, даже если телефон подключен к GlucoDataHandler на часах. + Без тревоги при подключ. Android Auto + Уведомления о тревоге не будут отображаться на телефоне, если подключен Android Auto. + Уведомления о тревоге будут отображаться на телефоне, даже если подключен Android Auto. + Повторный запуск + Если уведомление о тревоге не закрыто, тревога будет запущена снова через этот интервал в минутах до 3 раз.\n0 - без повторного запуска + старое значение + Тревога: устаревшие данные + Сигнал тревоги, если новые значения глюкозы не приходят дольше %1$s минут. + Это уведомление появится, если нет нового значения в течение интервала. + Не приходят данные! + + Отображение уведомлений отключено!!! + GlucoDataHandler нуждается в уведомлениях для выполнения задач в фоновом режиме и для отображения тревоги. + Уведомление о тревоге + Уведомления о тревоге. Пожалуйста, не изменяйте настройки, так как приложение предоставляет звук и вибрацию для уведомлений внутри. + Выбрать пользовательский звук + Без звука - без звука + Использовать пользовательский звук + Использовать выбранный пользовательский звук. + Использовать звук, предоставляемый приложением. + Задержка звука + Время в секундах до воспроизведения звука.\nВибрация будет работать все время, даже если звук не выбран. + Звук и вибрация + Только вибрация + Wear OS: Только вибрация + Уведомления отключены! + Чтобы отображать сигналы тревоги, GlucoDataHandler требуется как минимум включить канал уведомлений о тревоге.\nВы будете перенаправлены в настройки уведомлений для этого приложения.\nВключите там канал уведомлений о тревоге. + Внутренний звук приложения + Включить настройки + Включить настройки в сообщение для GlucoDataAuto.\nЭто перезапишет настройки в GlucoDataAuto. + Чтобы получать значения из GlucoDataHandler:\n- откройте GlucoDataHandler\n- перейдите в настройки\n- включите \"Переслать в GlucoDataAuto\"\n- чтобы использовать те же настройки, включите \"Включить настройки\" + Если вы используете уведомления Android Auto, сигналы тревоги об очень низком уровне всегда будут запускать уведомление, и это нельзя отключить или изменить. + IOB из Juggluco + Чтобы получать IOB из Juggluco:\n- откройте Juggluco\n- перейдите в \"Настройки\"\n- включите \"IOB\"\n- перейдите в \"Веб-сервер\"\n- включите \"Активный\"\n- необязательно: включите \"Только локально\" + IOB из xDrip+ + Чтобы получать значения из xDrip+:\n- откройте xDrip+\n- перейдите в настройки\n- перейдите в настройки взаимодействия приложений\n- включите \"Веб-сервис xDrip\" + Активировать использование IOB + Нажмите здесь, чтобы установить настройки Nightscout для использования локального веб-сервера для получения дополнительных значений IOB. + Перезаписать Nightscout + Это перезапишет ваши текущие настройки источника Nightscout для использования локального веб-сервиса на порту 17580 для получения значений IOB.\nВы уверены? + Переключатель уведомлений + Уведомления включены + Уведомления отключены + Нажмите воспроизведение, чтобы переключить уведомление + Настройки тревоги относятся только к уведомлениям в Android Auto и не запускают никаких звуков. + Общие настройки, такие как единицы измерения, время и настройки дельты. + Настройка предельных значений глюкозы и выбор соответствующего цвета. + Настройки обычных виджетов и плавающего виджета. + Настройки уведомлений. + Настройки для замены обоев экрана блокировки. + Настройки GlucoDataAuto для использования в Android Auto. + Часы MiBand и Amazfit + Часы Wear OS + Часы + Настройки для подключенных умных часов. + Настройки для передачи значений глюкозы в другие приложения. + Передача значений + Визуализация + Часы BangleJS + Чтобы использовать GlucoDataHandler на часах Wear OS, установите его из Google Play Store на часы. + Проверить соединение + Нажмите здесь, чтобы повторно проверить соединение с Wear OS.\nВы также можете запустить эту проверку, нажав на информацию о соединении на главном экране (даже в приложении Wear OS). + WatchDrip+ поддерживает некоторые модели устройств MiBand и Amazfit.\nЧтобы получить полный список поддерживаемых устройств и дополнительную информацию о настройке WatchDrip+, нажмите здесь. + Только вибрация без уведомления работает не на всех устройствах! + Использовать звук будильника + Использовать громкость звука будильника вместо звука мелодии звонка.\nЭто может быть независимо от режима вибрации. + Активируйте это, чтобы принудительно не воспроизводить звук тревоги, независимо от любых других настроек звука тревоги. + Задержка запуска + Задержка перед первым воспроизведением вибрации и звука.\nИногда уведомление с телефона немедленно останавливает вибрацию. + Цвет устаревшего значения + Цвет для отображения устаревшего значения глюкозы. + Без всплывающего окна, пока телефон подключен + Без дополнительного всплывающего уведомления на часах, пока телефон подключен.\nВибрация и звук будут запускаться в соответствии с настройками. + Сведения + Подключения + отключено + подключено + Тема приложения + Системная + Светлая + Темная + Действие при нажатии + Выбор действия или приложения, которое будет запускаться при нажатии на виджет. + Не реагировать + Переключить плавающий виджет + Действие по нажатию на усложнение + Отключите этот параметр, если стрелка тренда слишком большая или обрезана. + Пользовательский интерфейс + Общие настройки пользовательского интерфейса этого приложения. + GlucoDataAuto запущено + Включайте это только в том случае, если вы используете источник-фолловер и только уведомления Android Auto.\nЭто предотвращает закрытие приложения Android, пока оно не подключено к Android Auto, поэтому приложение не может распознать подключение Android Auto.\nВместо того, чтобы включать этот параметр, вы также можете один раз открыть приложение в Android Auto, если вы подключены. + Включена оптимизация батареи!\nНажмите здесь, чтобы отключить ее. + Если вы используете источник-фолловер, приложение может не запускаться при подключении к Android Auto, если вы используете только уведомления Android Auto.\nЧтобы приложение обнаруживало подключение Android Auto, необходимо отключить оптимизацию батареи.\nЕсли это по-прежнему не работает, вы также можете включить режим переднего плана или вам нужно один раз открыть приложение в Android Auto, если оно подключено к Android Auto. + Настройка Android Auto + Android Auto — это отдельное приложение или часть системы, к которому можно получить доступ через настройки Android.\nЧтобы активировать GlucoDataAuto для Android Auto, необходимо выполнить следующие действия: + 1. Активировать режим разработчика + - откройте Android Auto\n- прокрутите вниз до версии\n- нажмите несколько раз на версию, пока не появится всплывающее окно \"Разрешить настройки разработчика\"\n- нажмите \"ОК\" + 2. Активировать \"Неизвестные источники\" + - откройте Android Auto\n- откройте в меню из трех точек \"Настройки разработчика\"\n- прокрутите вниз до \"Неизвестный источник\"\n- включите его + 3. Настроить параметры уведомлений + - откройте Android Auto\n- прокрутите вниз до \"Сообщения\"\n- включите \"Показывать уведомления о сообщениях\"\n- включите \"Показывать первую строку сообщений\" + 4. Включить GlucoDataAuto + - откройте Android Auto\n- прокрутите вниз до \"Дисплей\"\n- откройте \"Настроить панель запуска\"\n- включите \"GlucoDataAuto\"\nЕсли GlucoDataAuto недоступен, перезагрузите телефон. + Цветная схема + Отображение значений крупным и цветным текстом.\nНа некоторых устройствах Samsung с Android 13 и выше это может привести к сбою приложения из-за ошибки на стороне Samsung.\nЕсли проблема возникает, этот параметр будет автоматически отключен, чтобы обеспечить работоспособность приложения. + Обнаружен сбой! + Мне очень жаль, что приложение вылетело во время выполнения!\nПожалуйста, отправьте мне логи, чтобы я мог проанализировать ошибку и попытаться исправить ее как можно скорее.\nСпасибо! + Dexcom Share + Американский аккаунт + Используется американский аккаунт. + Используется международный аккаунт. + Включить аккаунт Dexcom Share. Сначала вам потребуется указать имя пользователя и пароль. + Имя пользователя + Имя пользователя, адрес электронной почты или номер телефона для входа в вашу учетную запись Dexcom Share.\nВАЖНО: если вы используете номер телефона, он должен включать код страны (например, +1 для США). + Пароль для учетной записи Dexcom Share. + Настройка Dexcom Share + Отсутствует разрешение! + Настройка \'%1$s\' установлена, но соответствующее разрешение не предоставлено! Нажмите \"ОК\", чтобы перейти к диалоговому окну разрешений, или нажмите \"Отмена\", чтобы отключить эту настройку. + API службы вещания + Используйте API службы вещания xDrip+ для получения данных (также IOB) из xDrip+.\nСначала активируйте \"API службы вещания\" в \"Настройках взаимодействия приложений\" в xDrip+! + Время устаревания + Время в минутах, по истечении которого значение будет считаться устаревшим.\nПо истечении двух таких периодов времени значение перестанет отображаться. + Локальные приложения + Приложения на этом устройстве, предоставляющие значения глюкозы в виде внутреннего широковещательного сообщения, такие как Juggluco, xDrip+, AAPS и т. д. + Облачные сервисы, предоставляющие значения глюкозы через Интернет, такие как LibreLinkUp, Dexcom Share и Nightscout. + Альтернатива: локальная трансляция + Чтобы получать значения из Dexcom BYODA, вам необходимо включить трансляцию в AAPS при сборке приложения. + Чтобы получать значения из Eversense, вам необходимо использовать ESEL либо в режиме компаньона (чтение уведомлений), либо подключенным к пропатченному приложению Eversense.\nДля получения дополнительной информации об ESEL нажмите здесь. + Чтобы получать значения из Dexcom BYODA, вам необходимо включить трансляцию в xDrip+ при сборке приложения. + Доступна версия %s + Примечания + Уведомления отключены! \nНажмите здесь, чтобы включить их. + Добавить другую единицу измерения + Дополнительно показывать значение глюкозы в %s на главном экране. + Другая единица измерения + Нажмите здесь, чтобы проверить данные вашей учетной записи Dexcom Share для вашей учетной записи, не относящейся к США. + Нажмите здесь, чтобы проверить данные вашей учетной записи Dexcom Share для вашей учетной записи США. + Изменить размер данных экрана блокировки. + Сбросить соединение + Нажмите здесь, чтобы сбросить соединение между часами и телефоном, если оно работает не так, как ожидалось.\nЕсли сброс не решает проблему, перезагрузите часы. + Соединение работает не так, как ожидалось! Попробуйте перезагрузить часы. + Время до закрытия + Время в секундах до закрытия плавающего виджета при нажатии на виджет без его перемещения.\n0 для отключения этой настройки. + Включить режим тишины + Без уведомлений о тревоге для этой тревоги в режиме тишины.\nНастройте предпочтительное время для использования этой функции. + Без уведомлений о тревоге для всех тревог в режиме тишины.\nНастройте предпочтительное время для использования этой функции. + Время начала + Выберите время, в которое будет начинаться режим тишины. + Время окончания + Выберите время, в которое будет заканчиваться режим тишины. + Дни для режима тишины + Укажите дни недели, в которые режим тишины будет включаться в выбранное время. + Приложение вылетает из-за цветной схемы уведомлений!\nОна была отключена, чтобы предотвратить этот сбой.\nПерезагрузка телефона обычно это исправляет. + Автоматическое закрытие уведомлений + Автоматически закрывать уведомление о тревоге после завершения звука и вибрации, но не раньше, чем через 30 секунд.\nЕсли активирован повторный запуск, уведомление будет закрыто при условии отсутствия дополнительных триггеров. + + Настройки успешно сохранены + Ошибка при сохранении настроек! + Настройки успешно восстановлены + Ошибка при чтении настроек! + Экспорт / Импорт + Экспорт или импорт настроек и сохранение логов. + Экспорт + Нажмите здесь, чтобы экспортировать все настройки в файл. \nВАЖНО: будут экспортированы только измененные настройки! Настройки со значениями по умолчанию не будут включены в экспорт. + Импорт + Нажмите здесь, чтобы импортировать настройки из файла.\nВАЖНО: это не восстановит настройки по умолчанию, так как они не являются частью экспорта.\nЕсли вы хотите сбросить настройки до значений по умолчанию, сначала удалите данные приложения, а затем выполните импорт. + Сохранить логи с устройства в файл. + Сохранить логи с подключенных часов Wear OS в файл. + Повторить + Без вибрации + Режим вибрации + быстрый рост + Тревога: быстрый рост + Сигнал тревоги, если значение глюкозы выше или равно %1$s, а дельта увеличивается как минимум на +%2$s для %3$d последовательных показаний. + Быстрый рост! + + быстрое падение + Тревога: быстрое падение + Сигнал тревоги, если значение глюкозы ниже или равно %1$s, а дельта падает как минимум на -%2$s для %3$d последовательных показаний. + Быстрое падение! + Интенсивность вибрации + Интенсивность вибрации + Настройки сигналов тревоги + Значение дельты, при котором должен срабатывать сигнал тревоги. + Количество срабатываний + Количество раз, которое значение дельты должно быть достигнуто подряд. + Граница + Граница для сигнала тревоги при повышении/понижении.\nЗначение глюкозы должно быть выше/ниже, чтобы сработал сигнал тревоги. + в минуту + за 5 минут + Другие сигналы тревоги + Расширенные настройки сигналов тревоги + Внутренние сообщения из этого источника будут обрабатываться. + Внутренние сообщения из этого источника будут игнорироваться. + Временно отключено + Отложено до: + Временно отключено до: + + + Тренд + %1$d градусов вверх + %1$d градусов вниз + + Сигналы тревоги: %1$s + неактивен + отложено до %1$s + временно отключено до %1$s + + включено + выключено + недоступно + Озвучить данные + + Время %d минута + Время %d минуты + Время %d минут + Время %d минут + + Откройте «Интеграция» и включите «Общий доступ к данным с GWatch»\nПримечание: широковещательная рассылка DiaBox содержит только значение глюкозы и больше никакой информации, например, тренда! + Примечание: широковещательная рассылка AAPS от BYODA, похоже, не работает с другими приложениями. \nПожалуйста, используйте широковещательную рассылку xDrip+ в BYODA. + Начать работу с GDA в Android Auto + Преобразование текста в речь (TTS) не включено.\nЧтобы использовать эту функцию, включите TTS в настройках специальных возможностей Android.\nПосле включения нажмите здесь, чтобы обновить. + Озвучивание значения + Воспроизведение + Озвучивать текущее значение при воспроизведении.\nТребуется включить преобразование текста в речь. + Воспроизвести пустой звук, чтобы вывести медиаплеер на передний план разделенного экрана. + Озвучивать новое значение + Озвучивать новые значения в Android Auto, даже при использовании другого медиаплеера. + Нажмите, чтобы проверить преобразование текста в речь. + Озвучивать только сигналы тревоги. + Установите интервал для озвучивания новых значений в Android Auto. + Интервал для получения новых значений глюкозы используется в качестве длительности для индикатора выполнения в медиаплеере.\nУстановка значения 0 отключает индикатор выполнения. + Переключатель озвучивания нового значения + Озвучивание включено + Озвучивание выключено + Включить значок тревоги + Включить значок тревоги на главном экране для переключения состояния тревоги. + + + + Ошибка, пожалуйста, войдите снова в приложение LibreLinkUp и проверьте, есть ли какие-либо действия, которые необходимо выполнить! + Тип ошибки %1$s, пожалуйста, войдите снова в приложение LibreLinkUp и проверьте, есть ли какие-либо действия, которые необходимо выполнить! + Автоматически принимать новые условия + Включив эту опцию, вы автоматически принимаете новые Условия использования, чтобы обеспечить непрерывный прием данных. Вы не будете отдельно уведомлены о новых условиях. Если вы хотите их прочитать, отключите эту настройку и обратитесь к приложению LibreLinkUp в случае ошибки 4. + Необходимо принять новые Условия использования. Включите автоматическое принятие в настройках источника LibreLinkUp или войдите снова в приложение LibreLinkUp и примите условия. + GlucoDataHandler может автоматически принимать новые Условия использования для LibreLinkUp.\nНажмите OK, чтобы включить эту функцию, или Отмена, чтобы прочитать и вручную принять условия в приложении LibreLinkUp. + Неверное имя пользователя или пароль. Пожалуйста, проверьте настройки источника LibreLinkUp. При необходимости попробуйте войти снова или переустановить приложение LibreLinkUp и проверить настройки своей учетной записи.\nПожалуйста, остановите все другие приложения, которые обращаются к той же учетной записи! + Последнее показание глюкозы на сервере от %1$s + На сервере не найдено показаний глюкозы. + Слишком много запросов! + Пожалуйста, остановите все другие приложения, которые обращаются к той же учетной записи! + Ожидание данных + Отправлять уровень заряда батареи + Отправлять уровень заряда батареи на часы для отображения в усложнениях и на главном экране. + Показывать уровень заряда батареи + Установите минимальное время (в минутах) между измерениями глюкозы перед отправкой нового обновления на часы.\nСигналы тревоги и очень низкие измерения будут отправляться всегда. + Установите минимальное время (в минутах) между измерениями глюкозы перед отправкой нового обновления получателям. + Сигнал тревоги будет звучать и вибрировать повторно, пока вы не отключите уведомление или не будет достигнута установленная максимальная продолжительность сигнала тревоги. + Максимальная продолжительность сигнала тревоги + Укажите максимальное время (в минутах), в течение которого будет звучать и вибрировать сигнал тревоги. Значение 0 означает, что сигнал тревоги будет повторяться бесконечно. + Чтобы включить источники, сначала настройте их в приложении телефона. Активируйте источники на часах, только если вы еще не получаете данные на телефон. + Примечание разработчика + Это приложение было разработано в мое свободное время как любительский проект. Поэтому я не могу гарантировать, что все функции будут работать безупречно на каждом устройстве. Тем не менее, я постоянно работаю над улучшениями.\nЕсли у вас возникнут какие-либо проблемы или вопросы, пожалуйста, свяжитесь со мной напрямую, прежде чем оставлять отрицательный отзыв. \nЧтобы быть в курсе важных обновлений и исправлений ошибок, вы можете присоединиться к группе Facebook или Google Groups. + + Чтобы получать данные с серверов Dexcom Share:\nВ приложении Dexcom:\n - Создайте подписчика\n - В разделе «Подключения» убедитесь, что отображается «Поделиться» + В GlucoDataHandler:\n - Введите имя пользователя и пароль Dexcom Clarity\n (если вы используете номер телефона в качестве имени пользователя, укажите код страны)\n - Проверьте настройку для учетной записи США\nВажно: это не работает с учетной записью подписчика! + + Обновлено + Сигналы тревоги + + Циферблаты + Информация о сторонних циферблатах для Wear OS. + Настройка + Сторонние циферблаты + Это приложение не предоставляет циферблат, оно предоставляет несколько усложнений (небольшие круглые элементы циферблата), которые можно использовать для циферблатов, поддерживающих усложнения сторонних разработчиков. + Стандартные + Большинство производителей часов уже предоставляют циферблаты, которые можно использовать с усложнением GlucoDataHandler.\nНапример, большинство циферблатов Samsung "Info" совместимы. + Pujie + Pujie — это приложение для создания циферблатов с облачной библиотекой.\nЧтобы найти совместимые циферблаты, найдите в библиотеке циферблаты, начинающиеся с "GDH_".\nПримечание. Pujie несовместимо с устройствами Wear OS 5.\nНажмите здесь, чтобы открыть приложение в Play Маркете. + Diabetic Masked Man + Diabetic Masked Man создает сторонние циферблаты, специально разработанные для использования с GlucoDataHandler.\nНажмите здесь, чтобы открыть его страницу в Play Маркете. + Циферблаты GDC + Циферблаты GDC предназначены для использования с GlucoDataHandler.\nНажмите здесь, чтобы открыть страницу в Play Маркете. + Всегда обновлять усложнения + Всегда отправлять новые значения глюкозы на часы.\nОтключите эту опцию, чтобы предотвратить обновление данных на часах, когда экран часов выключен, для экономии заряда батареи.\nЗначения тревоги всегда будут отправляться на часы.\nВАЖНО: Устаревшая тревога отключается на часах, когда эта настройка отключена! + Всегда оповещать об очень низком + Эта тревога уведомит вас, даже если функция повтора или общий режим тишины для всех тревог активны, гарантируя, что вы будете предупреждены об очень низких уровнях глюкозы. + + Если эта ошибка повторяется, попробуйте переустановить приложение LibreLink. Это часто решает проблему. + Сервер + По умолчанию (.io) + Российский (.ru) + + Имя + Имя пользователя/пациента, которое должно отображаться в уведомлении и во время озвучивания.\nЭто следует использовать, если вы используете несколько вариантов GlucoDataAuto, в противном случае оно может быть пустым. + + Видеоинструкция + Нажмите здесь, чтобы перейти к видео на YouTube, которое иллюстрирует настройку этого источника. Большая благодарность Diabetic Masked Man за создание видео. + + 30 минут + 60 минут + 90 минут + 120 минут + Отложить в уведомлении + Выберите до 3 продолжительностей повтора для отображения в виде кнопок в уведомлении будильника. + + Показывать значения IOB/COB + Показывать значения IOB и COB, если они доступны. + + diff --git a/common/src/main/res/values-zh-rTW/strings.xml b/common/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000..033051501 --- /dev/null +++ b/common/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,715 @@ + + + zh-rTW + + 感測器 + 血糖值 + 原始值 + 變化量 + 速率 + 時間戳記 + IOB/COB 時間 + 時間差 + 警報 + 來源 + 每分鐘 + 尚未收到任何資料!\n請先設定資料來源 + + %1$d 分 + \> 1 時 + + 迅速上升 + 正在上升 + 緩慢上升 + 穩定 + 緩慢下降 + 正在下降 + 迅速下降 + + 確定 + 取消 + + - + 非常低 + + + 非常高 + + + + 已啟用電池最佳化 + 來源: 未啟用 + Wear: 已連線 (%1$s) + Wear: 已斷線 + Android Auto: 已連線 + Android Auto: 已斷線 + 已啟用協助工具-高對比顯示模式!\n按這裡設定 + %1$d 分鐘內沒有新血糖值! + + 設定 + 資料來源 + GitHub + 更新 + 支援 + 說明 + + + + 新血糖值 + 過時血糖值 + 血糖警報 + Wear 連線 + Android Auto 連線 + 血糖範圍變更 + + + + 手機 + Wear + + 未啟用 + 處理中 + 正常 + 無新血糖值 + 無網路連線 + 錯誤 + + 間隔 + 每隔多久就從資料來源請求資料 + 延遲 + 從雲端請求資料前的延遲秒數,因為有時候需要一些時間資料才會傳送到雲端 + + LibreLinkUp + 啟用 + 如果已設定使用者和密碼,則啟用 LibreLinkUp 追蹤器 + 電子郵件 + LibreLinkUp 登入用電子郵件 + 密碼 + LibreLinkUp 密碼 + 重新連線 + 下一次請求資料時會重新登入 + + Nightscout + 啟用 + 啟用 Nightscout 追蹤器 + Nightscout 網址 + Nightscout 伺服器網址 + API 密鑰 + 存取資料的 API 密鑰(選填) + 存取代符 + 如果需要的話加入您的存取代符 (Access Token)(選填) + + 不間斷 + 1 分鐘 + 5 分鐘 + 10 分鐘 + 15 分鐘 + 20 分鐘 + 30 分鐘 + 僅限警報 + + + 已停用 + 未連線 + 已連線 + 錯誤: %s + + + + + 一般 + 通知 + 小工具 + Android Auto + 轉發血糖值 + 目標範圍 + 顏色 + + 虛擬血糖值 + 非常高血糖 + 非常高血糖值的上限 + 高血糖 + 高血糖值的上限 + 目標血糖上限 + 目標範圍的上限 + 目標血糖下限 + 目標範圍的下限 + 非常低血糖 + 非常低血糖值的下限 + 低血糖 + 低血糖值的下限 + Android Auto 通知 + Android Auto 的通知 + 通知間隔 + 每隔多久顯示一次新血糖值通知\n警報會立刻顯示通知 + 僅限警報 + 僅限警報設定的時間間隔顯示通知\n非常低血糖警報會立刻顯示通知 + 偽裝為媒體播放器 + 於 Android Auto 的目前歌曲欄位顯示血糖值 + 傳送血糖資料廣播 + 從任何資料來源收到新血糖值時傳送血糖資料廣播 + 選擇血糖資料接收程式 + 選擇傳送血糖資料廣播的接收程式,若選擇全域廣播則會傳送資料到所有動態註冊的程式 + 傳送已修改 Libre 程式廣播 + 轉發血糖值到 xDrip+,在 xDrip+ 中選擇「Libre(已修改的程式)」作為資料來源 + 選擇已修改 Libre 程式廣播的接收程式 + 選擇傳送已修改 Libre 程式廣播的接收程式,若選擇全域廣播則會傳送資料到所有動態註冊的程式 + 傳送 xDrip+ 廣播 + 傳送類似 xDrip+ 傳送的廣播,例如傳送到 AAPS + 選擇 xDrip+ 廣播接收程式 + 選擇傳送 xDrip+ 廣播的接收程式,若選擇全域廣播則會傳送資料到所有動態註冊的程式 + 使用 mmol/l + 使用 mmol/l 取代 mg/dl + 5 分鐘變化量 + 開啟此選項會使用 5 分鐘變化量,如未開啟則使用 1 分鐘變化量 + 非常高/低警報顏色 + 非常高血糖值和非常低血糖值的顏色 + 高/低警報顏色 + 高血糖值和低血糖值的顏色 + 目標範圍顏色 + 在目標範圍內血糖值的顏色 + 通知 + 在狀態列顯示含目前血糖趨勢箭頭、血糖值和變化量的常駐通知\n顯示通知可避免 Android 將程式關閉 + 狀態列圖示 + 通知顯示於狀態列的圖示樣式 + 放大狀態列圖示 + 將狀態列圖示放大顯示,如果圖示太大導致被裁切請停用此設定 + 隱藏內容 + 移除通知內容,只顯示含有圖示的小型通知 + 第二通知 + 顯示無內容通知以便於狀態列顯示更多圖示 + 第二狀態列圖示 + 通知顯示於狀態列的圖示樣式 + 第三通知 + 第三狀態列圖示 + 浮動小工具 + 顯示含血糖值、趨勢箭頭、變化量、時間和 IOB/COB 的浮動小工具\n如果您按住不移動 5 秒以上可關閉浮動小工具 + 大小 + 變更浮動小工具大小 + 樣式 + 浮動小工具樣式 + 透明度 + 浮動小工具背景透明度 + 小工具透明度 + 小工具背景透明度 + 相對時間 + 小工具使用相對時間取代時間戳記\n相對時間可能因裝置的電池最佳化設定而無法正常運作 + + 程式圖示 + 血糖值 + 趨勢箭頭 + 變化量 + + Wear: 震動 + + 全域廣播 + + 全部顯示 + 找不到接收程式! + + 血糖值 + 血糖值、趨勢箭頭 + 血糖值、趨勢箭頭、變化量 + 血糖值、趨勢箭頭、變化量、時間戳記 + 血糖值、趨勢箭頭、變化量、時間、IOB/COB + + BangleJS 支援 + 轉發血糖值到 BangleJS 的 widbgjs 小工具 + + + + 小工具接收資料中 + + 手機: 已連線 (%1$s) + 手機: 已斷線 + + 前景 + 放大趨勢箭頭 + 彩色 AOD + + + + 血糖值 + 血糖值 (非常大、彩色) + 血糖值 (彩色) + 血糖值 (大) + 血糖值含圖示 + 血糖值、趨勢箭頭 + 血糖值、趨勢箭頭 (大、彩色) + 血糖值、趨勢箭頭 (大) + 血糖值、趨勢箭頭 (彩色) + 血糖值、趨勢範圍 (若支援) + 血糖值、變化量、趨勢範圍 (若支援) + 變化量、趨勢箭頭、趨勢範圍 (若支援) + 血糖值、變化量 + 血糖值、變化量、趨勢箭頭 + 血糖值、變化量、趨勢箭頭、時間戳記 + 血糖值、趨勢圖示、變化量、時間戳記 + 血糖值、趨勢值 + 變化量 (大) + 變化量 (大、彩色) + 變化量 + 變化量、趨勢箭頭 + 變化量含圖示 + 變化量、時間戳記 + 變化量、時間戳記 (彩色) + 變化量、時間戳記 (大) + 趨勢箭頭 (大、彩色) + 趨勢箭頭 (彩色) + 趨勢圖示 + 測試含負值趨勢範圍小工具 (若支援趨勢範圍) + 趨勢值 + 趨勢值、趨勢箭頭 + 趨勢值含圖示 + 血糖時間戳記 + 時間戳記 + 時間戳記 (彩色) + 時間戳記 (大) + 電量 (手錶和手機) + 手錶電量 + 手機電量 + IOB/COB + + + + https://github.com/pachi81/GlucoDataAuto/blob/main/README.md + 若要在 Android Auto 中使用,您必須多次點選「版本」以啟用 Android Auto 的開發人員選項,並於開發人員選項中啟用「未知來源」 + 資訊 + 若您使用 GlucoDataHandler 作為資料來源,下列設定會被 GlucoDataHandler 的設定覆蓋,所以不需要再次變更設定 + 缺少 GlucoDataAuto + 若要將 GlucoDataHandler 用於 Android Auto,您必須從 GitHub 安裝 GlucoDataAuto\n按這裡查看更多資訊並下載 GlucoDataAuto + 轉發到 GlucoDataAuto + 如果手機已連線到 Android Auto,則將血糖值和設定傳送到 GlucoDataAuto + + + + + 前景通知 + 前景通知可避免程式在背景執行時被關閉 + 第二通知 + 第三通知 + 更多通知以便於狀態列顯示更多圖示 + 工作通知 + 在背景執行任務時顯示工作通知 + 前景通知 + 前景通知可避免程式在背景執行時被關閉 + Android Auto 通知 + Android Auto 顯示的通知 + xDrip+ Inter-app Settings 中已啟用 Noise-Block!\n請變更為「Extremely noisy」! + 雲端服務 + Juggluco + 若要從 Juggluco 接收血糖值:\n- 開啟 Juggluco\n- 點選「Settings」\n- 啟用「Glucodata broadcast」\n- 啟用「de.michelinside.glucodatahandler」 + 若要從 Juggluco 接收血糖值:\n- 開啟 Juggluco\n- 點選「Settings」\n- 啟用「Glucodata broadcast」\n- 啟用「de.michelinside.glucodataauto」 + xDrip+ + 若要從 xDrip+ 接收血糖值:\n- 開啟 xDrip+\n- 點選 Settings\n- 前往 Inter-app Settings\n- 啟用「Broadcast locally」\n- 將「Noise Blocking」設為「Send even Extremely noisy signals」 + - 啟用「Compatible Broadcast」- 檢查「Identify receiver」是否為空或已存在內容,然後加入「de.michelinside.glucodatahandler」並以空白分隔 + - 啟用「Compatible Broadcast」\n- 檢查「Identify receiver」是否為空,然後新增一行「de.michelinside.glucodataauto」 + 設定 LibreLinkUp + 重要提示: 這不是登入 FreeStyle Libre 程式的帳號!\n若要啟用 LibreLinkUp 追蹤器:\n- 開啟 FreeStyle Libre 程式,並在選單中選擇「分享」或「已連結的應用程式」\n- 啟用 LibreLinkUp 連線\n- 從 Play 商店安裝 LibreLinkUp\n- 設定帳號並等候邀請 + 聯絡作者 + IOB/COB 支援 + 也從 Nightscout Pebble 端點接收 IOB 和 COB 值 + 程式需要顯示通知才能正常運作\nAndroid 13 以後可以滑動移除通知,如果您再次顯示通知,請將「狀態列圖示」設為「程式圖示」並啟用「隱藏內容」 + AAPS + AndroidAPS + 若要從 AAPS 接收血糖值:\n- 開啟 AAPS 程式\n- 點選「Config Builder」\n- 啟用「Samsung Tizen」或「Data Broadcaster」 + WatchDrip+ + 啟用 WatchDrip+ 通訊 (無圖表)\n重要提示: 在此啟用設定後,請在 WatchDrip+ 中啟用「Enable service」! + 重新顯示間隔 + 沒有新血糖值時每隔多久重新顯示通知 (0 代表永不顯示) + 虛擬媒體播放器圖示/圖片樣式 + 圖示/圖片樣式 + 手機 + Wear + 儲存記錄檔 + 記錄檔儲存成功 + 記錄檔儲存失敗! + Wear 記錄檔儲存成功 + Wear 記錄檔儲存失敗! + 需要鬧鐘和提醒權限以便處理所有與時間/間隔有關的工作\n僅用於程式內部排程,而不會新增或修改任何使用者設定的提醒\n按下「確定」將會打開權限設定,請手動將 GlucoDataHandler 設為啟用 + 鬧鐘和提醒權限 + 排程精確鬧鐘已停用!!!\nGlucoDataHandler 可能無法正常運作!!!\n按這裡前往權限設定 + + + 患者 + 如果有多個病患連線到 Libre 帳戶,請選擇要接收資料的患者 + 鎖定螢幕桌布 + 啟用 + 如果您啟用鎖定螢幕桌布,程式將會取代原本鎖定螢幕設定的桌布 + 垂直位置 + 鎖定螢幕上血糖趨勢箭頭的垂直位置:\n0 是螢幕的最上方\n100 是螢幕的最下方 + 顯示通知已停用!!!\nGlucoDataHandler 可能無法正常運作並無法顯示警報!!!\n按這裡前往權限設定 + + 缺少資料!請先在 LibreLinkUp 程式中接受邀請! + 彩色狀態列圖示 + 若您的裝置支援彩色狀態列圖示則可以啟用,如停用則會顯示白色圖示 + + 警報 + 間隔 + 此警報類型顯示通知的最小間隔 (分鐘)\n資訊: 只會在間隔時間已到且變化量為負時,才會顯示新通知 + 此警報類型顯示通知的最小間隔 (分鐘)\n資訊: 只會在間隔時間已到且變化量為正時,才會顯示新通知 + 自上次收到血糖值多久以後觸發此通知 (分鐘),間隔時間已到時會再次顯示 + 已啟用 + 此警報類型通知已啟用 + 此警報類型通知已停用 + 測試 + 按這裡 3 秒後會觸發此警報類型通知 + 設定 + 按這裡前往此警報的通知頻道設定\n可以在那裡變更所有相關設定 + 儲存聲音 + 按這裡可以將預設的警報聲音儲存到手機上 + 警報已儲存! + 貪睡 + 所有警報 + 警報通知 + Wear: 警報通知 + 手機上的警報通知已啟用\n提示: 警報會使用手機的鬧鐘音量 + 手機上的警報通知已停用 + 停止 + 非常低血糖! + 低血糖! + 高血糖! + 非常高血糖! + 非常低血糖警報 + 非常低血糖警報的通知 + 低血糖警報 + 低血糖警報的通知 + 高血糖警報 + 高血糖警報的通知 + 非常高血糖警報 + 非常高血糖警報的通知 + 關閉 + 測試警報! + 警報類別 + 全螢幕通知 + 啟用鎖定螢幕的全螢幕通知\n可能需要額外權限 + 關閉鍵盤防護 + 如果全螢幕通知運作時出現問題可以嘗試啟用此選項\n但某些裝置啟用此選項時,觸發全螢幕通知時會自動解鎖螢幕\n所以若您沒有遇到問題請不要啟用此選項 + 強制警報聲音 + 即使手機為靜音或震動模式,也會強制發出警報聲音和震動,還會強制停止勿擾模式!\n若要啟用此選項,系統可能需要允許變更勿擾模式的權限 + Waer: 強制警報聲音 + 強制震動 + 即使手機為靜音或震動模式,也會強制手機震動,還會強制停止勿擾模式!\n若要啟用此選項,系統可能需要允許變更勿擾模式的權限 + 音量大小 + 強制設定警報聲音的音量大小\n-1 表示使用預設設定 + 進階設定 + 全螢幕通知與聲音的進階警報設定 + 通知 + 聲音 + 從 %1$s 開始的血糖值警報設定 + Wear 裝置連線時不顯示警報 + 如果手機已與手錶的 GlucoDataHandler 連線,則手機不顯示警報通知 + 即使手機已與手錶的 GlucoDataHandler 連線,手機也會顯示警報通知 + Android Auto 連線時不顯示警報 + 如果 Android Auto 已連線,則手機不顯示警報通知 + 即使 Android Auto 已連線,手機也會顯示警報通知 + 重新觸發 + 如果警報通知未關閉,則警報將在設定的間隔 (分鐘) 後再次觸發 (最多 3 次)\n0 - 不重新觸發 + 血糖值過時 + 過時警報 + 如果 %1$s 分內沒有收到新血糖值則會顯示警報 + 如果在間隔時間後沒有收到新血糖值則會顯示此通知 + 沒有新血糖值! + + 顯示通知已停用!!! + GlucoDataHandler 需要啟用通知才能在背景執行工作並顯示警報 + 警報通知 + 警報通知。請勿變更設定,此為程式發出通知聲音和震動之用 + 選擇自訂聲音 + 靜音 - 沒有聲音 + 使用自訂聲音 + 使用選擇的自訂聲音 + 使用程式內建聲音 + 聲音延遲 + 播放聲音前的等待時間 (秒)\n即使未選擇聲音也會持續震動 + 聲音和震動 + 只有震動 + Wear: 只有震動 + 通知已停用! + 若要顯示警報 GlucoDataHandler 至少需要啟用警報通知頻道\n接下來會打開程式的通知設定\n請在那裡啟用警報通知頻道 + 內部程式聲音 + 包含設定 + 傳送到 GlucoDataAuto 的訊息中包含設定\n這會覆蓋 GlucoDataAuto 的設定 + 若要從 GlucoDataHandler 接收血糖值:\n- 開啟 GlucoDataHandler\n- 點選設定\n- 啟用「轉發到 GlucoDataAuto」\n- 若要使用同樣的設定,請啟用「包含設定」 + 如果您使用 Android Auto 通知,非常低血糖警報會強制啟用且無法停用或變更 + Juggluco 的 IOB + 若要從 Juggluco 接收 IOB:\n- 開啟 Juggluco\n- 點選「Settings」\n- 啟用「IOB」\n- 點選「Web server」\n- 啟用「Active」\n- 選用: 啟用「Local only」 + xDrip+ 的 IOB + 若要從 xDrip+ 接收血糖值:\n- 開啟 xDrip+\n- 前往 Settings\n- 點選 Inter-app settings\n- 啟用「xDrip Web Service」 + 啟用 IOB 使用 + 按這裡設定 Nightscout 設定,以使用本機網頁伺服器接收額外 IOB 值 + 覆寫 Nightscout + 覆寫目前的 Nightscout 資料來源設定,而使用連接埠 17580 的本機網頁服務接收 IOB 值\n您確定嗎? + 通知開關 + 通知已開啟 + 通知已關閉 + 按一下播放可開關通知 + 警報設定只會影響 Android Auto 相關的通知,不會發出任何聲音 + 一般設定,例如單位、時間和變化量設定 + 血糖上下限和對應顏色設定 + 手機小工具和浮動小工具設定 + 通知設定 + 取代鎖定螢幕桌布設定 + 用於 Android Auto 的 GlucoDataAuto 設定 + 小米手環和 Amazfit 手錶 + Wear OS 手錶 + 手錶 + 智慧手錶連線設定 + 傳輸血糖值到其他程式的設定 + 傳輸血糖值 + 視覺化 + BangleJS 手錶 + 若要在 Wear OS 手錶使用 GlucoDataHandler,請從手錶的 Google Play 商店安裝 + 檢查連線 + 按這裡重新檢查 Wear OS 連線\n在主畫面按一下連線資訊也可以檢查連線 (Wear OS 程式也適用)! + WatchDrip+ 支援多款小米手環和 Amazfit 裝置\n若要取得完整的支援裝置清單及更多關於設定 WatchDrip+ 的資訊,請按這裡 + 只震動而不顯示通知的功能並非在所有裝置都能正確運作! + 使用鬧鐘音量 + 使用鬧鐘的音量大小而不是鈴聲的音量大小\n此選項不受震動模式的影響 + 啟用此選項會強制不發出任何聲音,且不受其他警報聲音設定的影響 + 開始延遲 + 第一次播放震動和聲音前要延遲多久\n有時候手機通知會立即打斷震動 + 過時數值顏色 + 無新血糖值的顏色 + 手機連線時無彈出通知 + 手機連線時無額外 Wear 彈出通知\n震動和聲音會依照設定觸發 + 詳細資訊 + 連線 + 已斷線 + 已連線 + 程式配色 + 使用系統配色 + 淺色配色 + 深色配色 + 點選動作 + 選擇點選此項目時要執行的動作或程式 + 無動作 + 切換浮動小工具 + 小工具點選動作 + 如趨勢箭頭太大導致被裁切請停用此設定 + 使用者介面 + 程式使用者介面一般設定 + GlucoDataAuto 執行中 + 僅在您使用追蹤器資料來源且只使用 Android Auto 通知時才啟用\n這可以避免 Android 未連線到 Android Auto 時就將程式關閉,導致程式無法偵測 Android Auto 連線\n除了啟用此選項外,也可以在連線時於 Android Auto 中手動開啟程式一次 + 已啟用電池最佳化!\n按這裡停用 + 如果您使用追蹤器資料來源且只使用 Android Auto 通知時,程式可能無法在連線 Android Auto 時自動啟動\n若要確保程式能偵測 Android Auto 連線,必須停用電池最佳化\n如果仍然無法運作,也可以啟用前景模式,或在連線時於 Android Auto 中手動開啟程式一次 + 設定 Android Auto + Android Auto 可能是獨立的程式或整合於系統中且可從 Android 設定中存取\n若要於 Android Auto 啟用 GlucoDataAuto,您必須依照下列步驟: + 1. 啟用開發人員模式 + - 開啟 Android Auto\n- 向下找到「版本」\n- 多次點選版本直到出現「允許開發者設定」的彈出視窗\n- 按「確定」 + 2. 啟用「未知的來源」 + - 開啟 Android Auto\n- 在 3 點選單中點選「開發人員設定」\n- 向下找到「未知的來源」\n- 啟用該選項 + 3. 設定通知設定 + - 開啟 Android Auto\n- 向下找到「簡訊」\n- 啟用「顯示訊息通知」\n- 啟用「顯示訊息的第一行文字」 + 4. 啟用 GlucoDataAuto + - 開啟 Android Auto\n- 向下找到「螢幕」\n- 開啟「自訂啟動器」\n- 啟用「GlucoDataAuto」\n如果找不到 GlucoDataAuto,請重新啟動手機 + 彩色佈局 + 以放大彩色文字顯示血糖值\n某些搭載 Android 13 以上版本的 Samsung 裝置可能會使程式因 Samsung 系統的問題而當機\n如果發生問題此設定將自動停用,以確保程式功能正常 + 偵測到當機! + 很抱歉程式在執行時當機!\n請將記錄檔傳送給我,以便我能分析錯誤並盡快修復\n謝謝! + Dexcom Share + 美國帳號 + 已使用美國帳號 + 已使用國際帳號 + 啟用 Dexcom Share 帳號,您需要先設定使用者名稱和密碼 + 使用者名稱 + 用於登入 Dexcom Share 帳號的使用者名稱、電子郵件或電話號碼\n重要: 如果您使用電話號碼必須包含國家/地區代碼 (例如美國為 +1) + Dexcom Share 帳號密碼 + 設定 Dexcom Share + 缺少權限! + 「%1$s」已設定,但未授予相關權限!按「確定」會打開權限畫面,或是按「取消」停用此選項 + 廣播服務 API + 使用 xDrip+ 廣播服務 API 從 xDrip+ 接收資料 (包括 IOB)\n請先在 xDrip+ 的「Inter-App settings」中啟用「Broadcast Service API」! + 過時時間 + 設定幾分鐘後的血糖值視為過時血糖值\n兩倍的過時時間後,過時血糖值會被刪除 + 本機程式 + 在同裝置中以內部廣播訊息提供血糖值的程式,如 Juggluco、xDrip+、AAPS 等 + 透過網路的雲端服務提供血糖值,如 LibreLinkUp、Dexcom Share 和 Nightscout + 替代來源: 本機廣播 + 若要從 Dexcom BYODA 接收血糖值,您必須在建置程式時啟用對 AAPS 的廣播功能 + 若要從 Eversense 接收數值,您必須使用 ESEL,無論是在夥伴模式(讀取通知)還是連線到修補的 Eversense 應用程式。\n若要取得更多關於 ESEL 的資訊,請按這裡。 + 若要從 Dexcom BYODA 接收數值,您必須在建置應用程式時啟用對 xDrip+ 的廣播。 + 已有新版 %s 可用 + 備註 + 通知已停用!\n按這裡啟用 + 顯示其他單位 + 在詳細資訊中顯示 %s 為單位的血糖值 + 其他單位 + 按這裡檢查非美國帳號的 Dexcom Share 帳號資料 + 按這裡檢查美國帳號的 Dexcom Share 帳號資料 + 變更鎖定螢幕資料大小 + 重設連線 + 如果 Wear 和手機無法正常連線,請按這裡重設連線\n如果重設連線無法解決問題,請重新啟動手錶 + 連線不正常!請試著重新啟動手錶 + 關閉時間 + 按住小工具不移動幾秒後關閉浮動小工具\n0 表示停用 + 啟用勿擾時段 + 於勿擾時段期間,此警報不會發出通知\n設定您想要的時段以啟用此功能 + 於勿擾時段期間,所有警報都不會發出通知\n設定您想要的時段以啟用此功能 + 開始時間 + 選擇勿擾時段開始時間 + 結束時間 + 選擇勿擾時段結束時間 + 每週天數 + 設定每週幾勿擾時段會於開始時間生效 + + 程式因彩色佈局當機!\n為防止再次當機已停用此功能\n重新啟動手機通常可以解決這個問題。 + 自動關閉通知 + 在聲音和震動結束後自動關閉警報通知,但通知至少會顯示 30 秒。\n如果已啟用重新觸發功能,則會在重新觸發結束後才會自動關閉 + + 儲存設定成功 + 儲存設定失敗! + 讀取設定成功 + 讀取設定失敗! + 匯出 / 匯入 + 匯出或匯入設定並儲存記錄 + 匯出 + 按這裡會將所有設定匯出為檔案\n重要: 只有修改過的設定才會儲存!與預設值相同的設定不會匯出 + 匯入 + 按這裡會從檔案匯入所有設定\n重要: 預設設定不會重新建立,因為與預設值相同的設定不會匯出\n如果您想將所有設定重設為預設值,請先刪除應用程式資料再匯入 + + 將手機記錄存到記錄檔 + 將已連線 Wear OS 手錶的記錄存到記錄檔 + 重複 + 不要震動 + 震動模式 + + 快速上升 + 快速上升警報 + 如果血糖值大於等於 %1$s,且變化量連續 %3$d 次至少上升 +%2$s 就會發出警報 + 快速上升! + + 快速下降 + 快速下降警報 + 如果血糖值小於等於 %1$s,且變化量連續 %3$d 次至少下降 -%2$s 就會發出警報 + 快速下降! + 震動強度 + 震動強度 + 警報設定 + 要觸發警報的變化量 + 達成次數 + 變化量必須連續達成次數 + 上下限 + 上升/下降警報的上下限\n血糖值必須大於/小於此值才會發出警報 + 每分鐘 + 每 5 分鐘 + 其他警報 + 進階警報設定 + 將處理此資料來源的內部訊息 + 將忽略此資料來源的內部訊息 + 暫時停用 + 貪睡至 + 暫時停用至 + + 趨勢箭頭 + 上升 %1$d 度 + 下降 %1$d 度 + + 警報: %1$s + 未使用 + 貪睡至 %1$s + 暫時停用至 %1$s + + 已啟用 + 已停用 + + 無法使用 + + 讀數 + + + 時間 %d 分鐘 + + 開啟整合並啟用「與 GWatch 分享資料」\n備註: DiaBox 廣播只有血糖值,沒有趨勢箭頭等更多資訊! + 備註: 來自 BYODA 的 AAPS 廣播似乎無法與其他程式一起使用\n請在 BYODA 中使用 xDrip+ 廣播 + + 在 Android Auto 上開始使用 GDA + 未啟用文字轉語音 (TTS)\n若要使用此功能請在 Android 協助工具設定中啟用 TTS\n啟用後按這裡以更新狀態 + 朗讀血糖值 + 播放 + 播放時朗讀目前血糖值\n需要啟用文字轉語音 + 播放一段空白聲音,使媒體播放器顯示於分割畫面的前面 + 朗讀新血糖值 + 在 Android Auto 中朗讀新血糖值,即使使用其他媒體播放器時也會朗讀 + 按這裡測試文字轉語音 + 只朗讀警報 + 設定 Android Auto 中間隔多久朗讀新血糖值 + 於媒體播放器中進度列的顯示收到新血糖值的間隔時間\n設為 0 會停用進度列 + + 朗讀新數值開關 + 朗讀已開啟 + 朗讀已關閉 + 啟用警報圖示 + 在主畫面上啟用警報圖示以切換警報狀態 + + + + 錯誤,請重新登入 LibreLinkUp 程式並檢查是否有任何步驟未完成! + 錯誤類型 %1$s,請重新登入 LibreLinkUp 程式並檢查是否有任何步驟未完成! + 自動接受新條款 + 啟用此選項後您將自動接受新使用條款,確保可以持續接收資料。您不會再收到關於新條款的通知,如果您想閱讀新使用條款請停用此設定,並在發生錯誤 4 時打開 LibreLinkUp 程式 + 必須接受新使用條款。在 LibreLinkUp 資料來源設定中啟用自動接受,或再次登入 LibreLinkUp 程式並接受新條款 + GlucoDataHandler 可以自動接受 LibreLinkUp 的新使用條款。\n按「確定」啟用此功能,或按「取消」然後打開 LibreLinkUp 程式閱讀並手動接受條款 + 使用者名稱或密碼不正確。請確認 LibreLinkUp 資料來源設定。需要的話請試著再次登入或重新安裝 LibreLinkUp 程式並檢查您的帳號設定\n請關閉其他存取同一個帳號的程式! + 伺服器上的最後一次血糖讀數來自 %1$s + 伺服器找不到血糖讀數 + 太多請求! + 請關閉其他存取同一個帳號的程式! + 等待資料中 + 傳送電池電量 + 將電池電量傳送到手錶以便在小工具和主畫面上顯示 + 顯示電池電量 + 設定血糖測量最小間隔多久 (分鐘) 會傳送更新到手錶\n警報和非常低的血糖值會立即傳送 + 設定血糖測量最小間隔多久 (分鐘) 會傳送更新到接收程式 + 警報會重複發出聲音和震動,直到您關閉通知或達到所設定的最大警報持續時間為止 + 最大警報持續時間 + 設定警報發出聲音和震動的最大持續時間 (分鐘)。設為 0 表示警報會一直重複 + 要啟用資料來源,請優先在手機中設定。只有當您的手機收不到資料時,才從手錶啟用資料來源 + 開發者說明 + 本程式是我利用空閒時間所開發的專案,故無法保證所有功能都能在不同裝置上完美運作,但我會持續更新修正。\n如果您遇到任何問題或有任何疑問,請在留下負評之前與我聯繫。\n若要隨時掌握重大更新和錯誤修復,歡迎您加入 Facebook 社團或 Google 群組。 + + 若要從 Dexcom Share 伺服器接收資料:\n在 Dexcom 程式中:\n - 建立一個追蹤者\n - 在「Connections」中檢查是否有顯示「Share On」 + 在 GlucoDataHandler 中:\n - 輸入您的 Dexcom Clarity 使用者名稱和密碼\n (如果您使用手機號碼作為使用者名稱,請輸入國家/地區代碼)\n - 檢查美國帳號設定\n重要: 不可以使用追蹤者帳號! + + 已更新 + 警報 + 錶面 + 關於 Wear OS 第三方錶面資訊 + 設定 + 第三方錶面 + 本程式不提供錶面,但提供多種小工具 (錶面上的圓形部分),可用於支援第三方小工具的錶面 + 標準 + 多數手錶製造商都提供可以與 GlucoDataHandler 小工具一起使用的錶面\n如大多數 Samsung 的「資訊」錶面都可相容 + Pujie + Pujie 是一個有雲端資料庫的錶面設計程式\n若要尋找相容錶面,請在資料庫中搜尋以「GDH_」開頭的錶面\n注意: Pujie 與 Wear OS 5 裝置不相容\n按這裡開啟 Play 商店的程式 + Diabetic Masked Man + Diabetic Masked Man 專門設計可與 GlucoDataHandler 搭配使用的第三方錶面\n按這裡開啟他的 Play 商店頁面 + GDC 錶面 + GDC 錶面專門設計與 GlucoDataHandler 搭配使用\n按這裡開啟 Play 商店頁面 + 一律更新小工具 + 一律傳送新血糖值到手錶\n停用此選項可在手錶螢幕關閉時防止手錶更新資料以節省電量\n警報值一律會傳送到手錶\n重要: 停用此選項會關閉手錶的過時警報! + 非常低血糖時一律發出警報 + 即使處於貪睡模式或所有警報的勿擾時段,此警報仍會發出通知,確保您能收到非常低血糖的警報 + + 如果一直發生此錯誤,請試著重新安裝 Libre 程式,通常可以解決問題。 + 伺服器 + 預設 (.io) + 俄羅斯 (.ru) + + 姓名 + 使用者/病患的姓名,將會顯示在通知和語音播報中。\n如果您正在使用多個 GlucoDataAuto 變體,則應使用此選項,否則可以留空。 + + 影片教學 + 點擊此處前往 YouTube 影片,其中說明了此來源的設定。非常感謝 Diabetic Masked Man 製作這些影片。 + + 30 分鐘 + 60 分鐘 + 90 分鐘 + 120 分鐘 + 通知貪睡 + 選擇最多 3 個貪睡持續時間,以便在鬧鐘通知上顯示為按鈕。 + + 顯示 IOB/COB 值 + 顯示 IOB 和 COB 值(如果有的話)。 + + diff --git a/mobile/src/main/res/values/arrays.xml b/common/src/main/res/values/arrays.xml similarity index 86% rename from mobile/src/main/res/values/arrays.xml rename to common/src/main/res/values/arrays.xml index ea3ec0b8e..644ce6ed7 100644 --- a/mobile/src/main/res/values/arrays.xml +++ b/common/src/main/res/values/arrays.xml @@ -48,6 +48,7 @@ @string/obsolete_alarm_notification_name @string/falling_fast_alarm_notification_name @string/rising_fast_alarm_notification_name + @string/alarm_ignore_snooze_for_very_low @string/alarm_force_sound @string/alarm_force_vibration @string/alarm_vibrate_only @@ -67,6 +68,7 @@ "alarm_obsolete_enabled" "alarm_falling_fast_enabled" "alarm_rising_fast_enabled" + "alarm_force_very_low" "alarm_force_sound" "alarm_force_vibration" "alarm_vibrate_only" @@ -105,4 +107,24 @@ "30" "-1" + + @string/source_libre_server_default + @string/source_libre_server_russian + + + "io" + "ru" + + + "30" + "60" + "90" + "120" + + + @string/snooze_30 + @string/snooze_60 + @string/snooze_90 + @string/snooze_120 + diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 114c2dd47..f1ab78457 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -3,8 +3,60 @@ en GlucoDataHandler GlucoDataHandler + GDH Second + + mg/dl + mmol/l + + IOB + COB + I.O.B. + C.O.B. + + Facebook + Google Groups + + https://github.com/pachi81/GlucoDataHandler/blob/master/README.md + https://github.com/pachi81/GlucoDataHandler/releases + https://github.com/pachi81/GlucoDataHandler/issues + https://github.com/pachi81/GlucoDataAuto/issues + https://github.com/bigdigital/watchdrip/blob/main/README.md + https://github.com/BernhardRo/Esel/blob/master/README.md + https://myaccount.dexcom.eu + https://myaccount.dexcom.com + https://www.facebook.com/groups/823964236542396 + https://groups.google.com/g/glucodatahandler + https://play.google.com/store/apps/dev?id=7197840107055554214 + https://play.google.com/store/apps/dev?id=6551812103351455972 + https://play.google.com/store/apps/dev?id=7780011900367662392 + + https://youtu.be/YRs9nY4DgnA?si=v108UhnBNfQSeK2I + https://youtu.be/evS5rXDiciY?si=DTxlGYhYm-nNboHl + + + + Super Mario + Turtles + Final Fantasy + Star Wars + Power Rangers + James Bond + Mortal Kombat + + + Juggluco + xDrip+ + LibreLinkUp + Nightscout + Dexcom Share + Dexcom BYODA + Dexcom/Eversense + DiaBox + Eversense (ESEL)/Dexcom BYODA (xDrip+) + Dexcom BYODA (AAPS) + + - Alarm toggle Sensor Value Raw @@ -21,15 +73,6 @@ %1$d min \> 1h - - mg/dl - mmol/l - - IOB - COB - I.O.B. - C.O.B. - rising very quickly rising quickly rising @@ -40,7 +83,6 @@ OK Cancel - - very low @@ -65,17 +107,6 @@ Updates Support Help - Facebook - - https://github.com/pachi81/GlucoDataHandler/blob/master/README.md - https://github.com/pachi81/GlucoDataHandler/releases - https://github.com/pachi81/GlucoDataHandler/issues - https://github.com/pachi81/GlucoDataAuto/issues - https://github.com/bigdigital/watchdrip/blob/main/README.md - https://github.com/BernhardRo/Esel/blob/master/README.md - https://myaccount.dexcom.eu - https://myaccount.dexcom.com - https://www.facebook.com/groups/823964236542396 @@ -86,26 +117,8 @@ Android Auto connection Glucose range change - Super Mario - Turtles - Final Fantasy - Star Wars - Power Rangers - James Bond - Mortal Kombat - - Juggluco - xDrip+ - LibreLinkUp - Nightscout - Dexcom Share - Dexcom BYODA - Dexcom/Eversense - DiaBox - Eversense (ESEL)/Dexcom BYODA (xDrip+) - Dexcom BYODA (AAPS) Phone Wear @@ -348,7 +361,7 @@ - enable \"Compatible Broadcast\" - check \"Identify receiver\" to be empty or if an entry already exists, add \"de.michelinside.glucodatahandler\" separated by a space - enable \"Compatible Broadcast\"\n- check \"Identify receivers\" to be empty or add a new line with \"de.michelinside.glucodataauto\" Setup LibreLinkUp - IMPORTANT: this is not the account used for the FreeStyle Libre App!\nTo activate LibreLinkUp follower:\n- open your FreeStyle Libre App and select in the menu Share or Connected Apps\n- activate LibreLinkUp connection\n- install LibreLinkUp from PlayStore\n- setup your account and wait for the invitation + IMPORTANT: this is not the account used for the FreeStyle Libre App!\nTo activate LibreLinkUp follower:\n- open your FreeStyle Libre App and select in the menu Share or Connected Apps\n- activate LibreLinkUp connection\n- install LibreLinkUp from Play Store\n- setup your account and wait for the invitation Contact IOB/COB support Receive also IOB and COB values from nightscout pebble endpoint. @@ -426,8 +439,6 @@ Alarm categories Full screen notification Enables full screen notification on lockscreen.\nThis may request an additional permission. - Snooze on notification - If enabled, the notifications will also include buttons to activate snooze. Dismiss keyguard Please enable this setting, if you have problems with the fullscreen notification.\nOn some devices, this will cause the unlock screen, while the fullscreen notification is triggered.\nSo please do not enable it, if you do not have any problems. Force alarm sound @@ -504,7 +515,7 @@ Transfer values Visualisation BangleJS watches - To use GlucoDataHandler on Wear OS watches, please install it from Google Playstore on watch.\nThis app does not provide a watchface, it provides several complications (the small round watchface parts), which can be used for watchfaces supporting 3rd party complications. + To use GlucoDataHandler on Wear OS watches, please install it from Google Play Store on watch. Check connection Press here to re-check the connection to Wear OS.\nYou can also trigger this check while pressing on the connection info on home screen (even in the Wear OS app). WatchDrip+ supports several MiBand and Amazfit devices.\nTo get a whole list of supported devices and more information about configure WatchDrip+, please press here. @@ -561,7 +572,6 @@ Username, E-Mail or phone number for login to your Dexcom Share account.\nIMPORTANT: if you are using phone number, it has to include the country code (e.g. +1 for US). Password for Dexcom Share account. Setup Dexcom Share - To receive data from Dexcom Share servers, you need to have:\n- sharing enabled on the Dexcom application that is connected to the sensor\n- accepted the invitation on the Dexcom Follower app (you can uninstall it afterwards)\n\nImportant: it does not work with a follower user! Missing permission! The settings \'%1$s\' is set, but the related permission is not granted! Press \"OK\" to be forwared the permission dialog or press \"Cancel\" to disable this setting. Broadcast Service API @@ -578,9 +588,6 @@ Version %s is available Notes Notifications are disabled! \nPress here to enable them. - - - Show other unit Show glucose value in %s in the details. Other unit @@ -602,7 +609,7 @@ Days for quiet hours Specify the days of the week on which quiet hours should start at the selected start time. - The app crashes because of the colored notification layout!\nIt has been disabled to prevent this crash.\nThis is an issue on Samsung phones so please create a error report in the Samsung members app. Thank you! + The app crashes because of the colored notification layout!\nIt has been disabled to prevent this crash.\nA phone restart usually fixes this. Auto close notification Auto close alarm notification after sound and vibration has finished, but only after at least 30 seconds.\nIf retrigger is active, the notification will be closed, if there is no additional trigger. @@ -620,7 +627,6 @@ Save logs from the phone to file. Save logs from connected Wear OS watch to file. Repeat - "Repeat alarm sound and vibration. \n0: Disables the repeat functionality. \nPositive values: Set the repeat interval in minutes. \n-1: Enables unlimited repetition. " No vibration Vibration mode @@ -648,6 +654,8 @@ Internal messages from this source will be handled. Internal messages from this source will be ignored. Temporary disabled + Temporary disabled until + Snooze until Trend @@ -664,7 +672,7 @@ not available - Read values + Speak values Time %d minute @@ -673,8 +681,6 @@ Open Integration and enable \"Share data with GWatch\"\nRemark: the DiaBox broadcast only contains the glucose value and no more information like trend! Remark: the AAPS broadcast from BYODA doesn\'t seem to work with other apps. \nPlease use the xDrip+ broadcast in BYODA. - - Get started with GDA on Android Auto Text-to-speech (TTS) is not enabled.\nTo use this feature, enable TTS in Android Accessibility settings.\nOnce enabled, tap here to update. Speak Value @@ -694,4 +700,75 @@ Enable Alarm Icon Enable alarm icon on main screen to toggle alarm state. + + + Auto accept new terms + By enabling this, you automatically accept new Terms of Use to ensure continuous data reception. You won\'t be informed about new terms separately. If you want to read them, disable this setting and refer to the LibreLinkUp app in case of error 4. + GlucoDataHandler can automatically accept new Terms of Use for LibreLinkUp.\nPress OK to enable this feature, or Cancel to read and manually accept the terms in the LibreLinkUp app. + + Error, please re-login to LibreLinkUp App and check for any steps to be done! + Error type %1$s, please re-login to LibreLinkUp App and check for any steps to be done! + New Terms of Use must be accepted. Enable automatic acceptance in LibreLinkUp source settings, or log in again to the LibreLinkUp app and accept the terms. + Incorrect username or password. Please verify your LibreLinkUp source settings. If needed, try logging in again or reinstalling the LibreLinkUp app and checking your account settings.\nPlease stop all other apps accessing the same account! + Last glucose reading on server from %1$s + No glucose readings found on server. + Too many requests! + Please stop all other apps accessing the same account! + + Awaiting data + Send battery level + Send battery level to watch to show in complication and on main screen. + Show battery level + Set the minimum time (in minutes) between glucose measurements before sending a new update to the watch.\nAlarms and very low measurements will always be sent. + Set the minimum time (in minutes) between glucose measurements before sending a new update to the receivers. + The alarm will sound and vibrate repeatedly until you dismiss the notification or the set max alarm duration is reached. + Max alarm duration + Specify the maximum time (in minutes) the alarm will sound and vibrate. A value of 0 means the alarm will repeat indefinitely. + To enable sources, please set them up in the phone app first. Activate sources on your watch only if you don\'t receive the data on your phone yet. + Developer\'s Note + This app was developed in my free time as a hobby project. Therefore, I cannot guarantee that all features will work flawlessly on every device. However, I am constantly working on improvements.\nIf you encounter any problems or have questions, please contact me directly before leaving a negative review. \nTo stay informed about important updates and bug fixes, you are welcome to join the Facebook group or the Google Groups. + + To receive data from Dexcom Share servers:\nIn the Dexcom App:\n - Create a follower\n - Under \"Connections,\" ensure \"Share On\" is displayed + In GlucoDataHandler:\n - Enter your Dexcom Clarity username and password \n (if you use your phone number as your username, include your country code)\n - Check the setting for a US account\nImportant: It does not work with a follower account! + + Updated + Alarms + Watch Faces + Information about Wear OS third-party watch faces. + Setup + Third-party watch faces + This app does not provide a watch face, it provides several complications (the small round watch face parts), which can be used for watch faces supporting 3rd party complications. + Standard + Most watch manufacturers already provide watch faces that can be used with the GlucoDataHandler complication.\nFor example, most Samsung \"Info\" watch faces are compatible. + Pujie + Pujie is a watch face creator app with a cloud library.\nTo find compatible watch faces, search the library for watch faces starting with \"GDH_\".\nNote: Pujie is not compatible with Wear OS 5 devices.\nTap here to open the app in the Play Store. + Diabetic Masked Man + Diabetic Masked Man creates third-party watch faces specifically designed for use with GlucoDataHandler.\nTap here to open his Play Store page. + GDC Watch Faces + GDC Watch Faces are designed for use with GlucoDataHandler.\nTap here to open the Play Store page. + Always update complications + Always send new glucose values to watch.\nDisable this to prevent data updates on the watch while the watch screen is off to save battery.\nAlarm values will always be sent to the watch.\nIMPORTANT: The obsolete alarm is disabled on the watch when this setting is disabled! + Always alert for very low + This alarm will notify you even if snooze or the general quiet hours for all alarms are active, ensuring you are alerted to very low glucose levels. + + If this error persists, try reinstalling the Libre App. This often resolves the issue. + Server + Default (.io) + Russian (.ru) + + Name + Name of the user/patient, which should be shown in notification and during speaking.\nThis should be used, if you are using multiple variants of GlucoDataAuto, otherwise it could be empty. + + Video Tutorial + Click here to go to the YouTube video that illustrates the setup of this source. Many thanks to Diabetic Masked Man for creating the videos. + + 30 minutes + 60 minutes + 90 minutes + 120 minutes + Snooze on notification + Choose up to 3 snooze durations to display as buttons on the alarm notification. + Show IOB/COB values + Show IOB and COB values, if available. + diff --git a/mobile/src/main/res/xml/sources_online.xml b/common/src/main/res/xml/sources_online.xml similarity index 85% rename from mobile/src/main/res/xml/sources_online.xml rename to common/src/main/res/xml/sources_online.xml index c4d37fe7c..badbfd68f 100644 --- a/mobile/src/main/res/xml/sources_online.xml +++ b/common/src/main/res/xml/sources_online.xml @@ -29,6 +29,12 @@ android:title="@string/src_enabled" android:summary="@string/src_libre_enabled_summary" app:iconSpaceReserved="false" /> + + + + - + \ No newline at end of file diff --git a/common/src/second/res/mipmap-hdpi/ic_launcher_foreground.png b/common/src/second/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..43be625d7 Binary files /dev/null and b/common/src/second/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/common/src/second/res/mipmap-mdpi/ic_launcher_foreground.png b/common/src/second/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..22d8cf546 Binary files /dev/null and b/common/src/second/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/common/src/second/res/mipmap-xhdpi/ic_launcher_foreground.png b/common/src/second/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..1f1cea632 Binary files /dev/null and b/common/src/second/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/common/src/second/res/mipmap-xxhdpi/ic_launcher_foreground.png b/common/src/second/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..a034c9eca Binary files /dev/null and b/common/src/second/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/common/src/second/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/common/src/second/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..acf2cbf75 Binary files /dev/null and b/common/src/second/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/common/src/second/res/values/ic_launcher_background.xml b/common/src/second/res/values/ic_launcher_background.xml new file mode 100644 index 000000000..c5d5899fd --- /dev/null +++ b/common/src/second/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/images/GDH_SECOND_icon.png b/images/GDH_SECOND_icon.png new file mode 100644 index 000000000..0524451ec Binary files /dev/null and b/images/GDH_SECOND_icon.png differ diff --git a/images/GDH_SECOND_icon_white.png b/images/GDH_SECOND_icon_white.png new file mode 100644 index 000000000..29c64f287 Binary files /dev/null and b/images/GDH_SECOND_icon_white.png differ diff --git a/mobile/build.gradle b/mobile/build.gradle index 7c8ff9077..1e0c8c474 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -41,6 +41,7 @@ android { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' resValue "string", "app_name", "GDH Second" + signingConfig signingConfigs.debug } applicationVariants.all { // this method is use to rename your all apk weather @@ -80,11 +81,11 @@ dependencies { implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'com.google.android.material:material:1.12.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.constraintlayout:constraintlayout:2.2.0' implementation 'com.joaomgcd:taskerpluginlibrary:0.4.10' implementation project(path: ':common') implementation 'com.google.android.gms:play-services-tasks:18.2.0' - implementation 'com.google.android.gms:play-services-wearable:18.2.0' + implementation 'com.google.android.gms:play-services-wearable:19.0.0' implementation "androidx.car.app:app:1.4.0" implementation "androidx.preference:preference-ktx:1.2.1" implementation "com.jaredrummler:colorpicker:1.1.0" diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 386aff8a2..c61f82988 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -345,6 +345,15 @@ + + + + + 1 && elapsed < interval) { + Log.d(LOG_ID, "Ignore data because of interval $interval - elapsed: $elapsed") + return + } + if (sharedPref.getBoolean(Constants.SHARED_PREF_SEND_TO_XDRIP, false)) { val intent = Intent(Constants.XDRIP_ACTION_GLUCOSE_READING) // always sends time as start time, because it is only set, if the sensorId have changed! @@ -281,10 +315,6 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt intent.putExtras(extras) sendBroadcast(intent, Constants.SHARED_PREF_GLUCODATA_RECEIVERS, context, sharedPref) } - - if (sharedPref.getBoolean(Constants.SHARED_PREF_SEND_TO_BANGLEJS, false)) { - sendToBangleJS(context) - } } override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) { @@ -295,9 +325,10 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt context.setWearConnectionState(WearPhoneConnection.nodesConnected) } if (dataSource == NotifySource.CAR_CONNECTION && CarModeReceiver.connected) { - val autoExtras = ReceiveData.createExtras() - if (autoExtras != null) - CarModeReceiver.sendToGlucoDataAuto(context, autoExtras, true) + CarModeReceiver.sendToGlucoDataAuto(context, true) + } + if (dataSource == NotifySource.BATTERY_LEVEL) { + checkServices(context) } if (extras != null) { if (dataSource == NotifySource.MESSAGECLIENT || dataSource == NotifySource.BROADCAST) { @@ -308,4 +339,27 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt Log.e(LOG_ID, "OnNotifyData exception: " + exc.message.toString() ) } } + + override fun updateBatteryReceiver() { + try { + if(batteryReceiver == null) { + Log.i(LOG_ID, "register batteryReceiver") + batteryReceiver = BatteryReceiver() + registerReceiver(batteryReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) + } else { + if (!sharedPref!!.getBoolean(Constants.SHARED_PREF_BATTERY_RECEIVER_ENABLED, true)) { + Log.i(LOG_ID, "batteryReceiver disabled - keep active as watchdog") + // notify new battery level to update UI + BatteryReceiver.batteryPercentage = 0 + InternalNotifier.notify(this, NotifySource.BATTERY_LEVEL, BatteryReceiver.batteryBundle) + } else { + Log.i(LOG_ID, "batteryReceiver enabled - re-register") + unregisterReceiver(batteryReceiver) + registerReceiver(batteryReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) + } + } + } catch (exc: Exception) { + Log.e(LOG_ID, "updateBatteryReceiver exception: " + exc.toString()) + } + } } \ No newline at end of file diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt index fdcc5031f..c8925cd4c 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt @@ -1,7 +1,6 @@ package de.michelinside.glucodatahandler import android.annotation.SuppressLint -import android.app.AlarmManager import android.content.Context import android.content.Intent import android.content.SharedPreferences @@ -40,6 +39,8 @@ import de.michelinside.glucodatahandler.common.WearPhoneConnection import de.michelinside.glucodatahandler.common.notification.AlarmHandler import de.michelinside.glucodatahandler.common.notification.AlarmState import de.michelinside.glucodatahandler.common.notification.AlarmType +import de.michelinside.glucodatahandler.common.notification.ChannelType +import de.michelinside.glucodatahandler.common.notification.Channels import de.michelinside.glucodatahandler.common.notifier.DataSource import de.michelinside.glucodatahandler.common.notifier.InternalNotifier import de.michelinside.glucodatahandler.common.notifier.NotifierInterface @@ -70,10 +71,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface { private lateinit var tableDetails: TableLayout private lateinit var tableConnections: TableLayout private lateinit var tableAlarms: TableLayout - private lateinit var txtBatteryOptimization: TextView - private lateinit var txtHighContrastEnabled: TextView - private lateinit var txtScheduleExactAlarm: TextView - private lateinit var txtNotificationPermission: TextView + private lateinit var tableNotes: TableLayout private lateinit var btnSources: Button private lateinit var sharedPref: SharedPreferences private lateinit var optionsMenu: Menu @@ -100,14 +98,11 @@ class MainActivity : AppCompatActivity(), NotifierInterface { iobText = findViewById(R.id.iobText) cobText = findViewById(R.id.cobText) txtLastValue = findViewById(R.id.txtLastValue) - txtBatteryOptimization = findViewById(R.id.txtBatteryOptimization) - txtHighContrastEnabled = findViewById(R.id.txtHighContrastEnabled) - txtScheduleExactAlarm = findViewById(R.id.txtScheduleExactAlarm) - txtNotificationPermission = findViewById(R.id.txtNotificationPermission) btnSources = findViewById(R.id.btnSources) tableConnections = findViewById(R.id.tableConnections) tableAlarms = findViewById(R.id.tableAlarms) tableDetails = findViewById(R.id.tableDetails) + tableNotes = findViewById(R.id.tableNotes) PreferenceManager.setDefaultValues(this, R.xml.preferences, false) sharedPref = this.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) @@ -118,9 +113,13 @@ class MainActivity : AppCompatActivity(), NotifierInterface { txtVersion.text = BuildConfig.VERSION_NAME btnSources.setOnClickListener{ - val intent = Intent(this, SettingsActivity::class.java) - intent.putExtra(SettingsActivity.FRAGMENT_EXTRA, SettingsFragmentClass.SORUCE_FRAGMENT.value) - startActivity(intent) + try { + val intent = Intent(this, SettingsActivity::class.java) + intent.putExtra(SettingsActivity.FRAGMENT_EXTRA, SettingsFragmentClass.SORUCE_FRAGMENT.value) + startActivity(intent) + } catch (exc: Exception) { + Log.e(LOG_ID, "btn source exception: " + exc.message.toString() ) + } } Dialogs.updateColorScheme(this) @@ -162,15 +161,12 @@ class MainActivity : AppCompatActivity(), NotifierInterface { NotifySource.TIME_VALUE, NotifySource.ALARM_STATE_CHANGED, NotifySource.SOURCE_STATE_CHANGE)) - checkExactAlarmPermission() - checkBatteryOptimization() - checkHighContrast() checkFullscreenPermission() + checkNewSettings() if (requestNotificationPermission && Utils.checkPermission(this, android.Manifest.permission.POST_NOTIFICATIONS, Build.VERSION_CODES.TIRAMISU)) { Log.i(LOG_ID, "Notification permission granted") requestNotificationPermission = false - txtNotificationPermission.visibility = View.GONE PermanentNotification.showNotifications() } GlucoDataService.checkForConnectedNodes(true) @@ -191,13 +187,6 @@ class MainActivity : AppCompatActivity(), NotifierInterface { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !Utils.checkPermission(this, android.Manifest.permission.POST_NOTIFICATIONS, Build.VERSION_CODES.TIRAMISU)) { Log.i(LOG_ID, "Request notification permission...") requestNotificationPermission = true - /* - txtNotificationPermission.visibility = View.VISIBLE - txtNotificationPermission.setOnClickListener { - val intent: Intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) - .putExtra(Settings.EXTRA_APP_PACKAGE, this.packageName) - startActivity(intent) - }*/ if (this.shouldShowRequestPermissionRationale( android.Manifest.permission.POST_NOTIFICATIONS)) { Dialogs.showOkDialog(this, CR.string.permission_notification_title, CR.string.permission_notification_message) { _, _ -> requestPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS) } @@ -205,30 +194,31 @@ class MainActivity : AppCompatActivity(), NotifierInterface { this.requestPermissions(arrayOf(android.Manifest.permission.POST_NOTIFICATIONS), 3) } return false - } else { - txtNotificationPermission.visibility = View.GONE } requestExactAlarmPermission() return true } - private fun canScheduleExactAlarms(): Boolean { - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val alarmManager = this.getSystemService(Context.ALARM_SERVICE) as AlarmManager - return alarmManager.canScheduleExactAlarms() - } - return true - } - private fun requestExactAlarmPermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !canScheduleExactAlarms()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !Utils.canScheduleExactAlarms(this)) { Log.i(LOG_ID, "Request exact alarm permission...") val builder: AlertDialog.Builder = AlertDialog.Builder(this) builder .setTitle(CR.string.request_exact_alarm_title) .setMessage(CR.string.request_exact_alarm_summary) .setPositiveButton(CR.string.button_ok) { dialog, which -> - startActivity(Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + try { + startActivity( + Intent( + ACTION_REQUEST_SCHEDULE_EXACT_ALARM, + Uri.parse("package:$packageName") + ) + ) + } catch (exc: Exception) { + Log.e(LOG_ID, "requestExactAlarmPermission exception: " + exc.message.toString() ) + } + } } .setNegativeButton(CR.string.button_cancel) { dialog, which -> // Do something else. @@ -237,59 +227,6 @@ class MainActivity : AppCompatActivity(), NotifierInterface { dialog.show() } } - private fun checkExactAlarmPermission() { - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !canScheduleExactAlarms()) { - Log.w(LOG_ID, "Schedule exact alarm is not active!!!") - txtScheduleExactAlarm.visibility = View.VISIBLE - txtScheduleExactAlarm.setOnClickListener { - startActivity(Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM)) - } - } else { - txtScheduleExactAlarm.visibility = View.GONE - Log.i(LOG_ID, "Schedule exact alarm is active") - } - } catch (exc: Exception) { - Log.e(LOG_ID, "checkBatteryOptimization exception: " + exc.message.toString() ) - } - } - - private fun checkBatteryOptimization() { - try { - val pm = getSystemService(POWER_SERVICE) as PowerManager - if (!pm.isIgnoringBatteryOptimizations(packageName)) { - Log.w(LOG_ID, "Battery optimization is active") - txtBatteryOptimization.visibility = View.VISIBLE - txtBatteryOptimization.setOnClickListener { - val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - intent.data = Uri.parse("package:$packageName") - startActivity(intent) - } - } else { - txtBatteryOptimization.visibility = View.GONE - Log.i(LOG_ID, "Battery optimization is inactive") - } - } catch (exc: Exception) { - Log.e(LOG_ID, "checkBatteryOptimization exception: " + exc.message.toString() ) - } - } - private fun checkHighContrast() { - try { - if (Utils.isHighContrastTextEnabled(this)) { - Log.w(LOG_ID, "High contrast is active") - txtHighContrastEnabled.visibility = View.VISIBLE - txtHighContrastEnabled.setOnClickListener { - val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS) - startActivity(intent) - } - } else { - txtHighContrastEnabled.visibility = View.GONE - Log.i(LOG_ID, "High contrast is inactive") - } - } catch (exc: Exception) { - Log.e(LOG_ID, "checkBatteryOptimization exception: " + exc.message.toString() ) - } - } private fun checkFullscreenPermission() { if(sharedPref.contains(Constants.SHARED_PREF_ALARM_FULLSCREEN_NOTIFICATION_ENABLED) && sharedPref.getBoolean(Constants.SHARED_PREF_ALARM_FULLSCREEN_NOTIFICATION_ENABLED, true)) { @@ -309,6 +246,49 @@ class MainActivity : AppCompatActivity(), NotifierInterface { } } + private fun checkNewSettings() { + try { + if(!sharedPref.contains(Constants.SHARED_PREF_DISCLAIMER_SHOWN)) { + Dialogs.showOkDialog(this, + CR.string.gdh_disclaimer_title, + CR.string.gdh_disclaimer_message, + null + ) + with(sharedPref.edit()) { + putString(Constants.SHARED_PREF_DISCLAIMER_SHOWN, BuildConfig.VERSION_NAME) + apply() + } + } + if(!sharedPref.contains(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU)) { + if(sharedPref.getBoolean(Constants.SHARED_PREF_LIBRE_ENABLED, false)) { + Dialogs.showOkCancelDialog(this, + resources.getString(CR.string.src_cat_libreview), + resources.getString(CR.string.src_libre_tou_message), + { _, _ -> + with(sharedPref.edit()) { + putBoolean(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU, true) + apply() + } + }, + { _, _ -> + with(sharedPref.edit()) { + putBoolean(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU, false) + apply() + } + }) + } else { + with(sharedPref.edit()) { + putBoolean(Constants.SHARED_PREF_LIBRE_AUTO_ACCEPT_TOU, true) + apply() + } + } + } + + } catch (exc: Exception) { + Log.e(LOG_ID, "checkNewSettings exception: " + exc.message.toString() ) + } + } + override fun onCreateOptionsMenu(menu: Menu?): Boolean { try { Log.v(LOG_ID, "onCreateOptionsMenu called") @@ -389,6 +369,14 @@ class MainActivity : AppCompatActivity(), NotifierInterface { } return true } + R.id.action_google_groups -> { + val browserIntent = Intent( + Intent.ACTION_VIEW, + Uri.parse(resources.getText(CR.string.google_gdh_group_url).toString()) + ) + startActivity(browserIntent) + return true + } R.id.action_facebook -> { val browserIntent = Intent( Intent.ACTION_VIEW, @@ -483,6 +471,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface { } } updateAlarmIcon() + updateAlarmsTable() } catch (exc: Exception) { Log.e(LOG_ID, "updateAlarmIcon exception: " + exc.message.toString() ) } @@ -538,9 +527,9 @@ class MainActivity : AppCompatActivity(), NotifierInterface { deltaText.text = "Δ ${ReceiveData.getDeltaAsString()}" iobText.text = "💉 " + ReceiveData.getIobAsString() iobText.contentDescription = getString(CR.string.info_label_iob) + " " + ReceiveData.getIobAsString() - cobText.text = "🍔 " + ReceiveData.getCobAsString() - iobText.contentDescription = getString(CR.string.info_label_cob) + " " + ReceiveData.getCobAsString() iobText.visibility = if (ReceiveData.isIobCobObsolete()) View.GONE else View.VISIBLE + cobText.text = "🍔 " + ReceiveData.getCobAsString() + cobText.contentDescription = getString(CR.string.info_label_cob) + " " + ReceiveData.getCobAsString() cobText.visibility = iobText.visibility txtLastValue.visibility = if(ReceiveData.time>0) View.GONE else View.VISIBLE @@ -551,6 +540,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface { btnSources.visibility = View.GONE } + updateNotesTable() updateAlarmsTable() updateConnectionsTable() updateDetailsTable() @@ -567,6 +557,81 @@ class MainActivity : AppCompatActivity(), NotifierInterface { update() } + private fun updateNotesTable() { + tableNotes.removeViews(1, maxOf(0, tableNotes.childCount - 1)) + if (!Channels.notificationChannelActive(this, ChannelType.MOBILE_FOREGROUND)) { + val onClickListener = OnClickListener { + try { + if (!Channels.notificationChannelActive(this, ChannelType.MOBILE_FOREGROUND)) { + requestNotificationPermission = true + val intent: Intent = if (Channels.notificationActive(this)) { // only the channel is inactive! + Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) + .putExtra(Settings.EXTRA_APP_PACKAGE, this.packageName) + .putExtra(Settings.EXTRA_CHANNEL_ID, ChannelType.MOBILE_FOREGROUND.channelId) + } else { + Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) + .putExtra(Settings.EXTRA_APP_PACKAGE, this.packageName) + } + startActivity(intent) + } else { + updateNotesTable() + } + } catch (exc: Exception) { + Log.e(LOG_ID, "updateNotesTable exception: " + exc.message.toString() ) + if(requestPermission()) { + GlucoDataServiceMobile.start(this) + updateNotesTable() + } + } + } + tableNotes.addView(createRow(CR.string.activity_main_notification_permission, onClickListener)) + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !Utils.canScheduleExactAlarms(this)) { + Log.w(LOG_ID, "Schedule exact alarm is not active!!!") + val onClickListener = OnClickListener { + try { + startActivity( + Intent( + ACTION_REQUEST_SCHEDULE_EXACT_ALARM, + Uri.parse("package:$packageName") + ) + ) + } catch (exc: Exception) { + Log.e(LOG_ID, "Schedule exact alarm exception: " + exc.message.toString() ) + } + } + tableNotes.addView(createRow(CR.string.activity_main_schedule_exact_alarm, onClickListener)) + } + if (Utils.isHighContrastTextEnabled(this)) { + Log.w(LOG_ID, "High contrast is active") + val onClickListener = OnClickListener { + try { + val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS) + startActivity(intent) + } catch (exc: Exception) { + Log.e(LOG_ID, "High contrast alarm exception: " + exc.message.toString() ) + } + } + tableNotes.addView(createRow(CR.string.activity_main_high_contrast_enabled, onClickListener)) + } + val pm = getSystemService(POWER_SERVICE) as PowerManager + if (!pm.isIgnoringBatteryOptimizations(packageName)) { + Log.w(LOG_ID, "Battery optimization is active") + val onClickListener = OnClickListener { + try { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + intent.data = Uri.parse("package:$packageName") + startActivity(intent) + } catch (exc: Exception) { + Log.e(LOG_ID, "Battery optimization exception: " + exc.message.toString() ) + } + } + tableNotes.addView(createRow(CR.string.activity_main_battery_optimization_disabled, onClickListener)) + } + checkTableVisibility(tableNotes) + } + private fun updateConnectionsTable() { tableConnections.removeViews(1, maxOf(0, tableConnections.childCount - 1)) if (SourceStateData.lastState != SourceState.NONE) { @@ -577,25 +642,33 @@ class MainActivity : AppCompatActivity(), NotifierInterface { msg ) ) - if(SourceStateData.lastState == SourceState.ERROR && SourceStateData.lastSource == DataSource.DEXCOM_SHARE) { - if (msg.contains("500:")) { // invalid password + if(SourceStateData.lastState == SourceState.ERROR) { + if(SourceStateData.lastSource == DataSource.DEXCOM_SHARE && msg.contains("500:")) { val us_account = sharedPref.getBoolean(Constants.SHARED_PREF_DEXCOM_SHARE_USE_US_URL, false) val browserIntent = Intent( Intent.ACTION_VIEW, Uri.parse(resources.getString(if(us_account)CR.string.dexcom_account_us_url else CR.string.dexcom_account_non_us_url)) ) val onClickListener = OnClickListener { - startActivity(browserIntent) + try { + startActivity(browserIntent) + } catch (exc: Exception) { + Log.e(LOG_ID, "Dexcom browse exception: " + exc.message.toString() ) + } } tableConnections.addView( createRow( - SourceStateData.lastSource.resId, resources.getString(if(us_account) CR.string.dexcom_share_check_us_account else CR.string.dexcom_share_check_non_us_account), onClickListener ) ) } } + if(SourceStateData.lastErrorInfo.isNotEmpty()) { + // add error specific information in an own row + tableConnections.addView(createRow(SourceStateData.lastErrorInfo)) + } + tableConnections.addView(createRow(CR.string.request_timestamp, Utils.getUiTimeStamp(SourceStateData.lastStateTime))) } if (WearPhoneConnection.nodesConnected) { @@ -608,8 +681,8 @@ class MainActivity : AppCompatActivity(), NotifierInterface { val onCheckClickListener = OnClickListener { GlucoDataService.checkForConnectedNodes(false) } - WearPhoneConnection.getNodeBatterLevels().forEach { (name, level) -> - tableConnections.addView(createRow(name, if (level > 0) "$level%" else "?%", onCheckClickListener)) + WearPhoneConnection.getNodeConnectionStates(this).forEach { (name, state) -> + tableConnections.addView(createRow(name, state, onCheckClickListener)) } } } @@ -634,9 +707,9 @@ class MainActivity : AppCompatActivity(), NotifierInterface { tableAlarms.addView(createRow(CR.string.info_label_alarm, resources.getString(deltaAlarmType.resId))) } if (AlarmHandler.isTempInactive) - tableAlarms.addView(createRow(CR.string.temp_disabled, AlarmHandler.inactiveEndTimestamp)) + tableAlarms.addView(createRow(CR.string.temp_disabled_until, AlarmHandler.inactiveEndTimestamp)) else if (AlarmHandler.isSnoozeActive) - tableAlarms.addView(createRow(CR.string.snooze, AlarmHandler.snoozeTimestamp)) + tableAlarms.addView(createRow(CR.string.snooze_until, AlarmHandler.snoozeTimestamp)) checkTableVisibility(tableAlarms) } @@ -646,8 +719,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface { if (!ReceiveData.isObsoleteLong() && sharedPref.getBoolean(Constants.SHARED_PREF_SHOW_OTHER_UNIT, false)) { tableDetails.addView(createRow(ReceiveData.getOtherUnit(), ReceiveData.getGlucoseAsOtherUnit() + " (Δ " + ReceiveData.getDeltaAsOtherUnit() + ")")) } - tableDetails.addView(createRow(CR.string.info_label_timestamp, DateFormat.getTimeInstance( - DateFormat.DEFAULT).format(Date(ReceiveData.time)))) + tableDetails.addView(createRow(CR.string.info_label_timestamp, Utils.getUiTimeStamp(ReceiveData.time))) if (!ReceiveData.isIobCobObsolete(1.days.inWholeSeconds.toInt())) tableDetails.addView(createRow(CR.string.info_label_iob_cob_timestamp, DateFormat.getTimeInstance( DateFormat.DEFAULT).format(Date(ReceiveData.iobCobTime)))) @@ -692,6 +764,19 @@ class MainActivity : AppCompatActivity(), NotifierInterface { return row } + private fun createRow(valueResId: Int, onClickListener: OnClickListener? = null) : TableRow { + return createRow(resources.getString(valueResId), onClickListener) + } + + private fun createRow(value: String, onClickListener: OnClickListener? = null) : TableRow { + val row = TableRow(this) + row.weightSum = 1f + //row.setBackgroundColor(resources.getColor(R.color.table_row)) + row.setPadding(Utils.dpToPx(5F, this)) + row.addView(createColumn(value, false, onClickListener)) + return row + } + private fun checkUncaughtException() { Log.i(LOG_ID, "Check uncaught exception exists: ${sharedPref.getBoolean(Constants.SHARED_PREF_UNCAUGHT_EXCEPTION_DETECT, false)} - " + "last occured at ${DateFormat.getDateTimeInstance().format(Date(sharedPref.getLong(Constants.SHARED_PREF_UNCAUGHT_EXCEPTION_TIME, 0)))}") diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/android_auto/CarModeReceiver.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/android_auto/CarModeReceiver.kt index f1065b8ea..e90f9fe0b 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/android_auto/CarModeReceiver.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/android_auto/CarModeReceiver.kt @@ -6,7 +6,6 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.Build -import android.os.Bundle import android.util.Log import androidx.car.app.connection.CarConnection import de.michelinside.glucodatahandler.common.* @@ -35,7 +34,7 @@ object CarModeReceiver { class GDAReceiver: BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - Log.d(LOG_ID, "onReceive called for intent " + intent + ": " + Utils.dumpBundle(intent.extras)) + Log.i(LOG_ID, "onReceive called for intent " + intent + ": " + Utils.dumpBundle(intent.extras)) if(!PackageUtils.isGlucoDataAutoAvailable(context)) { Log.i(LOG_ID, "GlucoDataAuto not available, but package received -> update packages") PackageUtils.updatePackages(context) @@ -109,21 +108,28 @@ object CarModeReceiver { } } - fun sendToGlucoDataAuto(context: Context, extras: Bundle, withSettings: Boolean = false) { - val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) - if (connected && sharedPref.getBoolean(Constants.SHARED_PREF_SEND_TO_GLUCODATAAUTO, true) && PackageUtils.isGlucoDataAutoAvailable(context)) { - Log.d(LOG_ID, "sendToGlucoDataAuto") - val intent = Intent(Intents.GLUCODATA_ACTION) - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) - if(withSettings && sharedPref.getBoolean(Constants.SHARED_PREF_SEND_PREF_TO_GLUCODATAAUTO, true)) { - val settings = GlucoDataService.getSettings() - settings.putBoolean(Constants.SHARED_PREF_RELATIVE_TIME, ElapsedTimeTask.relativeTime) - extras.putBundle(Constants.SETTINGS_BUNDLE, settings) - extras.putBundle(Constants.ALARM_SETTINGS_BUNDLE, AlarmHandler.getSettings(true)) + fun sendToGlucoDataAuto(context: Context, withSettings: Boolean = false) { + try { + val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) + if (connected && sharedPref.getBoolean(Constants.SHARED_PREF_SEND_TO_GLUCODATAAUTO, true) && PackageUtils.isGlucoDataAutoAvailable(context)) { + val extras = ReceiveData.createExtras(false) + if(extras != null) { + Log.i(LOG_ID, "send to ${Constants.PACKAGE_GLUCODATAAUTO}") + val intent = Intent(Intents.GLUCODATA_ACTION) + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) + if(withSettings && sharedPref.getBoolean(Constants.SHARED_PREF_SEND_PREF_TO_GLUCODATAAUTO, true)) { + val settings = GlucoDataService.getSettings() + settings.putBoolean(Constants.SHARED_PREF_RELATIVE_TIME, ElapsedTimeTask.relativeTime) + extras.putBundle(Constants.SETTINGS_BUNDLE, settings) + extras.putBundle(Constants.ALARM_SETTINGS_BUNDLE, AlarmHandler.getSettings(true)) + } + intent.putExtras(extras) + intent.setPackage(Constants.PACKAGE_GLUCODATAAUTO) + context.sendBroadcast(intent) + } } - intent.putExtras(extras) - intent.setPackage(Constants.PACKAGE_GLUCODATAAUTO) - context.sendBroadcast(intent) + } catch (exc: Exception) { + Log.e(LOG_ID, "sendToGlucoDataAuto exception: " + exc.message.toString() ) } } } diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/notification/AlarmNotification.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/notification/AlarmNotification.kt index 87bdb6b43..22d57c324 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/notification/AlarmNotification.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/notification/AlarmNotification.kt @@ -58,7 +58,8 @@ object AlarmNotification : AlarmNotificationBase() { .setUsage(AudioAttributes.USAGE_ALARM) .build() channel.setSound(null, audioAttributes) - channel.enableVibration(false) + channel.enableVibration(true) + channel.vibrationPattern = longArrayOf(0) channel.enableLights(true) } @@ -86,7 +87,7 @@ object AlarmNotification : AlarmNotificationBase() { val resId = getAlarmTextRes(alarmType) val contentView = RemoteViews(GlucoDataService.context!!.packageName, R.layout.alarm_notification) contentView.setTextViewText(R.id.alarm, context.getString(resId!!)) - contentView.setTextViewText(R.id.snooze, context.getString(CR.string.snooze)) + contentView.setTextViewText(R.id.snoozeText, context.getString(CR.string.snooze)) if(alarmType == AlarmType.OBSOLETE) { contentView.setTextViewText(R.id.deltaText, "🕒 ${ReceiveData.getElapsedTimeMinuteAsString(context)}") contentView.setContentDescription(R.id.deltaText, ReceiveData.getElapsedTimeMinuteAsString(context)) @@ -101,9 +102,6 @@ object AlarmNotification : AlarmNotificationBase() { contentView.setContentDescription(R.id.trendImage, ReceiveData.getRateAsText(context)) contentView.setTextViewText(R.id.deltaText, "Δ " + ReceiveData.getDeltaAsString()) } - contentView.setOnClickPendingIntent(R.id.snooze_60, createSnoozeIntent(context, 60L, getNotificationId(alarmType))) - contentView.setOnClickPendingIntent(R.id.snooze_90, createSnoozeIntent(context, 90L, getNotificationId(alarmType))) - contentView.setOnClickPendingIntent(R.id.snooze_120, createSnoozeIntent(context, 120L, getNotificationId(alarmType))) if (ReceiveData.isObsoleteShort()) { if (!ReceiveData.isObsoleteLong()) contentView.setInt(R.id.glucose, "setPaintFlags", Paint.STRIKE_THRU_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG) @@ -111,6 +109,24 @@ object AlarmNotification : AlarmNotificationBase() { } if (getAddSnooze()) { + val snoozeButtons = getSnoozeValues() + + if(snoozeButtons.size>0) { + createSnoozeButton(contentView, context, R.id.snooze_60, snoozeButtons.elementAt(0), getNotificationId(alarmType)) + } else { + contentView.setViewVisibility(R.id.snooze_60, View.GONE) + } + if(snoozeButtons.size>1) { + createSnoozeButton(contentView, context, R.id.snooze_90, snoozeButtons.elementAt(1), getNotificationId(alarmType)) + } else { + contentView.setViewVisibility(R.id.snooze_90, View.GONE) + } + if(snoozeButtons.size>2) { + createSnoozeButton(contentView, context, R.id.snooze_120, snoozeButtons.elementAt(2), getNotificationId(alarmType)) + } else { + contentView.setViewVisibility(R.id.snooze_120, View.GONE) + } + val bigContentView = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { RemoteViews(contentView) } else { @@ -122,11 +138,18 @@ object AlarmNotification : AlarmNotificationBase() { notificationBuilder.setCustomBigContentView(null) } contentView.setViewVisibility(R.id.snoozeLayout, View.GONE) + contentView.setViewVisibility(R.id.snoozeText, View.GONE) notificationBuilder.setCustomContentView(contentView) } else if (getAddSnooze()) { - notificationBuilder.addAction(createSnoozeAction(context, context.getString(CR.string.snooze) + ": 60", 60L, getNotificationId(alarmType))) - notificationBuilder.addAction(createSnoozeAction(context, "90", 90L, getNotificationId(alarmType))) - notificationBuilder.addAction(createSnoozeAction(context, "120", 120L, getNotificationId(alarmType))) + var first = true + getSnoozeValues().forEach { + if(first) { + notificationBuilder.addAction(createSnoozeAction(context, context.getString(CR.string.snooze) + ": $it", it, getNotificationId(alarmType))) + first = false + } else { + notificationBuilder.addAction(createSnoozeAction(context, it.toString(), it, getNotificationId(alarmType))) + } + } } if (fullscreenEnabled && hasFullscreenPermission()) { @@ -141,6 +164,13 @@ object AlarmNotification : AlarmNotificationBase() { } } + + private fun createSnoozeButton(contentView: RemoteViews, context: Context, resId: Int, snooze: Long, noticationId: Int) { + contentView.setOnClickPendingIntent(resId, createSnoozeIntent(context, snooze, noticationId)) + contentView.setContentDescription(resId, context.resources.getString(CR.string.snooze) + " " + snooze) + contentView.setTextViewText(resId, snooze.toString()) + } + override fun stopNotificationForRetrigger(): Boolean { return LockscreenActivity.isActive() } diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/notification/LockscreenActivity.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/notification/LockscreenActivity.kt index cf73fa85c..21ff1e456 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/notification/LockscreenActivity.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/notification/LockscreenActivity.kt @@ -43,9 +43,9 @@ class LockscreenActivity : AppCompatActivity(), NotifierInterface { private lateinit var btnDismiss: SlideToActView private lateinit var btnClose: Button private lateinit var btnSnooze: SlideToActView - private lateinit var btnSnooze60: Button - private lateinit var btnSnooze90: Button - private lateinit var btnSnooze120: Button + private lateinit var btnSnooze1: Button + private lateinit var btnSnooze2: Button + private lateinit var btnSnooze3: Button private lateinit var layoutSnooze: LinearLayout private lateinit var layoutSnoozeButtons: LinearLayout private var alarmType: AlarmType? = null @@ -97,17 +97,18 @@ class LockscreenActivity : AppCompatActivity(), NotifierInterface { btnClose = findViewById(R.id.btnClose) btnSnooze = findViewById(R.id.btnSnooze) txtSnooze = findViewById(R.id.txtSnooze) - btnSnooze60 = findViewById(R.id.btnSnooze60) - btnSnooze90 = findViewById(R.id.btnSnooze90) - btnSnooze120 = findViewById(R.id.btnSnooze120) + btnSnooze1 = findViewById(R.id.btnSnooze60) + btnSnooze2 = findViewById(R.id.btnSnooze90) + btnSnooze3 = findViewById(R.id.btnSnooze120) layoutSnooze = findViewById(R.id.layoutSnooze) layoutSnoozeButtons = findViewById(R.id.layoutSnoozeButtons) val sharedPref = this.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) - if (sharedPref.getBoolean(Constants.SHARED_PREF_ALARM_SNOOZE_ON_NOTIFICATION, false)) - layoutSnooze.visibility = View.VISIBLE - else + val snoozeValues = AlarmNotification.getSnoozeValues() + if (snoozeValues.isEmpty()) layoutSnooze.visibility = View.GONE + else + layoutSnooze.visibility = View.VISIBLE if(this.isScreenReaderOn()) { Log.d(LOG_ID, "Screen reader is on!") @@ -115,9 +116,6 @@ class LockscreenActivity : AppCompatActivity(), NotifierInterface { btnClose.visibility = View.VISIBLE btnSnooze.visibility = View.GONE txtSnooze.visibility = View.GONE - btnSnooze60.contentDescription = resources.getString(CR.string.snooze) + " 60" - btnSnooze90.contentDescription = resources.getString(CR.string.snooze) + " 90" - btnSnooze120.contentDescription = resources.getString(CR.string.snooze) + " 120" layoutSnoozeButtons.visibility = View.VISIBLE btnClose.setOnClickListener{ Log.d(LOG_ID, "Stop button clicked!") @@ -141,24 +139,24 @@ class LockscreenActivity : AppCompatActivity(), NotifierInterface { object : SlideToActView.OnSlideCompleteListener { override fun onSlideComplete(view: SlideToActView) { Log.d(LOG_ID, "Slide to snooze completed!") - AlarmNotification.stopVibrationAndSound() + AlarmNotification.stopForLockscreenSnooze() btnSnooze.visibility = View.GONE txtSnooze.visibility = View.VISIBLE layoutSnoozeButtons.visibility = View.VISIBLE } } } - btnSnooze60.setOnClickListener{ - AlarmHandler.setSnooze(60) - stop() + btnSnooze1.visibility = View.GONE + btnSnooze2.visibility = View.GONE + btnSnooze3.visibility = View.GONE + if(snoozeValues.size>0) { + createSnoozeButton(btnSnooze1, snoozeValues.elementAt(0)) } - btnSnooze90.setOnClickListener{ - AlarmHandler.setSnooze(90) - stop() + if(snoozeValues.size>1) { + createSnoozeButton(btnSnooze2, snoozeValues.elementAt(1)) } - btnSnooze120.setOnClickListener{ - AlarmHandler.setSnooze(120) - stop() + if(snoozeValues.size>2) { + createSnoozeButton(btnSnooze3, snoozeValues.elementAt(2)) } delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES @@ -171,6 +169,16 @@ class LockscreenActivity : AppCompatActivity(), NotifierInterface { } } + private fun createSnoozeButton(button: Button, snooze: Long) { + button.visibility = View.VISIBLE + button.text = snooze.toString() + button.contentDescription = resources.getString(CR.string.snooze) + " " + snooze.toString() + button.setOnClickListener{ + AlarmHandler.setSnooze(snooze) + stop() + } + } + override fun onDestroy() { try { Log.v(LOG_ID, "onDestroy called") diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/notification/PermanentNotification.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/notification/PermanentNotification.kt index 33704dd18..35f91a3f7 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/notification/PermanentNotification.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/notification/PermanentNotification.kt @@ -27,6 +27,7 @@ import de.michelinside.glucodatahandler.common.notifier.NotifySource import de.michelinside.glucodatahandler.common.notification.ChannelType import de.michelinside.glucodatahandler.common.notification.Channels import de.michelinside.glucodatahandler.common.utils.PackageUtils +import de.michelinside.glucodatahandler.common.R as CR object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceChangeListener { @@ -92,7 +93,7 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe Channels.getNotificationManager().cancel(THIRD_NOTIFICATION_ID) secondNotificationCompat = Notification.Builder(context, ChannelType.MOBILE_SECOND.channelId) - .setSmallIcon(R.mipmap.ic_launcher) + .setSmallIcon(CR.mipmap.ic_launcher) .setOngoing(true) .setOnlyAlertOnce(true) .setAutoCancel(false) @@ -103,7 +104,7 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe .setVisibility(Notification.VISIBILITY_PUBLIC) thirdNotificationCompat = Notification.Builder(context, ChannelType.MOBILE_THIRD.channelId) - .setSmallIcon(R.mipmap.ic_launcher) + .setSmallIcon(CR.mipmap.ic_launcher) .setOngoing(true) .setOnlyAlertOnce(true) .setAutoCancel(false) @@ -114,7 +115,7 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe .setVisibility(Notification.VISIBILITY_PUBLIC) foregroundNotificationCompat = Notification.Builder(context, ChannelType.MOBILE_FOREGROUND.channelId) - .setSmallIcon(R.mipmap.ic_launcher) + .setSmallIcon(CR.mipmap.ic_launcher) .setOngoing(true) .setOnlyAlertOnce(true) .setAutoCancel(false) @@ -136,14 +137,22 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe val bigIcon = sharedPref.getBoolean(Constants.SHARED_PREF_PERMANENT_NOTIFICATION_USE_BIG_ICON, false) val coloredIcon = sharedPref.getBoolean(Constants.SHARED_PREF_PERMANENT_NOTIFICATION_COLORED_ICON, true) return when(sharedPref.getString(iconKey, StatusBarIcon.APP.pref)) { - StatusBarIcon.GLUCOSE.pref -> BitmapUtils.getGlucoseAsIcon(roundTarget=!bigIcon, color = if(coloredIcon) ReceiveData.getGlucoseColor() else Color.WHITE, withShadow = if(coloredIcon) true else false) + StatusBarIcon.GLUCOSE.pref -> BitmapUtils.getGlucoseAsIcon( + roundTarget=!bigIcon, + color = if(coloredIcon) ReceiveData.getGlucoseColor() else Color.WHITE, + withShadow = coloredIcon, + useTallFont = true + ) StatusBarIcon.TREND.pref -> BitmapUtils.getRateAsIcon( color = if(coloredIcon) ReceiveData.getGlucoseColor() else Color.WHITE, resizeFactor = if (bigIcon) 1.5F else 1F, - withShadow = if(coloredIcon) true else false + withShadow = coloredIcon ) - StatusBarIcon.DELTA.pref -> BitmapUtils.getDeltaAsIcon(roundTarget=!bigIcon, color = if(coloredIcon) ReceiveData.getGlucoseColor(true) else Color.WHITE) - else -> Icon.createWithResource(GlucoDataService.context, R.mipmap.ic_launcher) + StatusBarIcon.DELTA.pref -> BitmapUtils.getDeltaAsIcon( + roundTarget=!bigIcon, + color = if(coloredIcon) ReceiveData.getGlucoseColor(true) else Color.WHITE, + useTallFont = true) + else -> Icon.createWithResource(GlucoDataService.context, CR.mipmap.ic_launcher) } } diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmAdvancedFragment.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmAdvancedFragment.kt index 98fea4a9a..b06724611 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmAdvancedFragment.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmAdvancedFragment.kt @@ -41,10 +41,10 @@ class AlarmAdvancedFragment : SettingsFragmentCompatBase(), SharedPreferences.On override fun onResume() { Log.d(LOG_ID, "onResume called") try { + super.onResume() preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) updateEnableStates(preferenceManager.sharedPreferences!!) update() - super.onResume() } catch (exc: Exception) { Log.e(LOG_ID, "onResume exception: " + exc.toString()) } @@ -53,8 +53,8 @@ class AlarmAdvancedFragment : SettingsFragmentCompatBase(), SharedPreferences.On override fun onPause() { Log.d(LOG_ID, "onPause called") try { - preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) super.onPause() + preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) } catch (exc: Exception) { Log.e(LOG_ID, "onPause exception: " + exc.toString()) } diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmFragment.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmFragment.kt index df0c6ae66..06b1f8e9d 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmFragment.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmFragment.kt @@ -90,9 +90,9 @@ class AlarmFragment : SettingsFragmentCompatBase(), SharedPreferences.OnSharedPr override fun onResume() { Log.d(LOG_ID, "onResume called") try { + super.onResume() preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) update() - super.onResume() } catch (exc: Exception) { Log.e(LOG_ID, "onResume exception: " + exc.toString()) } @@ -101,8 +101,8 @@ class AlarmFragment : SettingsFragmentCompatBase(), SharedPreferences.OnSharedPr override fun onPause() { Log.d(LOG_ID, "onPause called") try { - preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) super.onPause() + preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) } catch (exc: Exception) { Log.e(LOG_ID, "onPause exception: " + exc.toString()) } diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmTypeFragment.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmTypeFragment.kt index a1ff6aa81..bf98e1172 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmTypeFragment.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmTypeFragment.kt @@ -56,11 +56,11 @@ class AlarmTypeFragment : SettingsFragmentCompatBase(), SharedPreferences.OnShar private val intervalPref: String get() { return getPrefKey(Constants.SHARED_PREF_ALARM_SUFFIX_INTERVAL) } - /*private val repeatPref: String get() { + private val repeatPref: String get() { + return getPrefKey(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT_UNTIL_CLOSE) + } + private val repeatTimePref: String get() { return getPrefKey(Constants.SHARED_PREF_ALARM_SUFFIX_REPEAT) - }*/ - private val retriggerPref: String get() { - return getPrefKey(Constants.SHARED_PREF_ALARM_SUFFIX_RETRIGGER) } private val soundDelayPref: String get() { return getPrefKey(Constants.SHARED_PREF_ALARM_SUFFIX_SOUND_DELAY) @@ -129,10 +129,10 @@ class AlarmTypeFragment : SettingsFragmentCompatBase(), SharedPreferences.OnShar override fun onResume() { Log.d(LOG_ID, "onResume called") try { + super.onResume() preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) InternalNotifier.addNotifier(requireContext(), this, mutableSetOf(NotifySource.NOTIFICATION_STOPPED)) update() - super.onResume() } catch (exc: Exception) { Log.e(LOG_ID, "onResume exception: " + exc.toString()) } @@ -141,11 +141,11 @@ class AlarmTypeFragment : SettingsFragmentCompatBase(), SharedPreferences.OnShar override fun onPause() { Log.d(LOG_ID, "onPause called") try { + super.onPause() stopTestSound() Vibrator.cancel() preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) InternalNotifier.remNotifier(requireContext(), this) - super.onPause() } catch (exc: Exception) { Log.e(LOG_ID, "onPause exception: " + exc.toString()) } @@ -170,7 +170,7 @@ class AlarmTypeFragment : SettingsFragmentCompatBase(), SharedPreferences.OnShar startTestSound() } else if( key == vibrateAmplitudePref) { alarmType.setting!!.updateSettings(sharedPreferences) - Vibrator.vibrate(alarmType.setting!!.vibratePattern!!, -1, alarmType.setting!!.vibrateAmplitude) + Vibrator.vibrate(alarmType.setting!!.vibratePattern!!, -1, alarmType.setting!!.vibrateAmplitude, AlarmNotification.useAlarmStream) } } catch (exc: Exception) { Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.toString()) @@ -188,11 +188,11 @@ class AlarmTypeFragment : SettingsFragmentCompatBase(), SharedPreferences.OnShar val prefUseCustomRingtone = findPreference(useCustomSoundPref) prefSelectRingtone!!.isEnabled = prefUseCustomRingtone!!.isChecked updateRingtoneSelectSummary() - /* - val prefRepeat = findPreference(repeatPref) - val prefRetrigger = findPreference(retriggerPref) - prefRetrigger!!.isEnabled = prefRepeat!!.value >= 0 - */ + + val prefRepeat = findPreference(repeatPref) + val prefRepeatTime = findPreference(repeatTimePref) + prefRepeatTime!!.isEnabled = prefRepeat!!.isChecked + val prefWeekdays = findPreference(inactiveWeekdaysPref) prefWeekdays!!.summary = resources.getString(CR.string.alarm_inactive_weekdays_summary) + "\n" + prefWeekdays.values.joinToString( ", " @@ -268,15 +268,17 @@ class AlarmTypeFragment : SettingsFragmentCompatBase(), SharedPreferences.OnShar prefInterval!!.value = preferenceManager.sharedPreferences!!.getInt(prefInterval.key, AlarmHandler.getDefaultIntervalMin(alarmType)) prefInterval.summary = getIntervalSummary(alarmType) - val prefRetrigger = findPreference(retriggerPref) - prefRetrigger!!.value = preferenceManager.sharedPreferences!!.getInt(prefRetrigger.key, 0) - - /* - val prefRepeat = findPreference(repeatPref) - prefRepeat!!.value = preferenceManager.sharedPreferences!!.getInt(prefRepeat.key, 0) + val prefRepeat = findPreference(repeatPref) + val prefRepeatTime = findPreference(repeatTimePref) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - prefRepeat.isVisible = false // looping is supported for API 28 and above only - }*/ + prefRepeat!!.isVisible = false // looping is supported for API 28 and above only + prefRepeat.isChecked = false + prefRepeatTime!!.isVisible = false + prefRepeatTime.value = 0 + } else { + prefRepeat!!.isChecked = preferenceManager.sharedPreferences!!.getBoolean(prefRepeat.key, alarmType.setting!!.repeatUntilClose) + prefRepeatTime!!.value = preferenceManager.sharedPreferences!!.getInt(prefRepeatTime.key, alarmType.setting!!.repeatTime) + } val prefSoundDelay = findPreference(soundDelayPref) prefSoundDelay!!.value = preferenceManager.sharedPreferences!!.getInt(prefSoundDelay.key, 0) @@ -289,7 +291,7 @@ class AlarmTypeFragment : SettingsFragmentCompatBase(), SharedPreferences.OnShar val prefVibrateAmplitude = findPreference(vibrateAmplitudePref) prefVibrateAmplitude!!.value = preferenceManager.sharedPreferences!!.getInt(prefVibrateAmplitude.key, 15) - prefVibrateAmplitude.isVisible = Vibrator.vibrator.hasAmplitudeControl() + prefVibrateAmplitude.isVisible = Vibrator.hasAmplitudeControl() val levelPref = findPreference(soundLevelPref) diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/ExportImportSettingsFragment.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/ExportImportSettingsFragment.kt index 98308c98c..14c969b48 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/ExportImportSettingsFragment.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/ExportImportSettingsFragment.kt @@ -34,67 +34,79 @@ class ExportImportSettingsFragment: SettingsFragmentBase(R.xml.pref_export_impor const val IMPORT_PHONE_SETTINGS = 4 } override fun initPreferences() { - Log.v(LOG_ID, "initPreferences called") - - var downloadUri: Uri? = null - MediaScannerConnection.scanFile(requireContext(), arrayOf( - Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_DOWNLOADS - ).absolutePath), null - ) { s: String, uri: Uri -> - Log.v(LOG_ID, "Set URI $uri for path $s") - downloadUri = uri - } + try { + Log.v(LOG_ID, "initPreferences called") + + var downloadUri: Uri? = null + MediaScannerConnection.scanFile(requireContext(), arrayOf( + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS + ).absolutePath), null + ) { s: String, uri: Uri -> + Log.v(LOG_ID, "Set URI $uri for path $s") + downloadUri = uri + } - val prefExportSettings = findPreference(Constants.SHARED_PREF_EXPORT_SETTINGS) - prefExportSettings!!.setOnPreferenceClickListener { - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = "text/plain" - val currentDateandTime = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format( - Date() - ) - val fileName = "GDH_phone_settings_" + currentDateandTime - putExtra(Intent.EXTRA_TITLE, fileName) - putExtra(DocumentsContract.EXTRA_INITIAL_URI, downloadUri) + val prefExportSettings = findPreference(Constants.SHARED_PREF_EXPORT_SETTINGS) + prefExportSettings!!.setOnPreferenceClickListener { + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "text/plain" + val currentDateandTime = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format( + Date() + ) + val fileName = (if(Constants.IS_SECOND) "GDH_SECOND_" else "GDH_") + "phone_settings_" + currentDateandTime + putExtra(Intent.EXTRA_TITLE, fileName) + putExtra(DocumentsContract.EXTRA_INITIAL_URI, downloadUri) + } + startActivityForResult(intent, EXPORT_PHONE_SETTINGS) + true } - startActivityForResult(intent, EXPORT_PHONE_SETTINGS) - true - } - val prefImportSettings = findPreference(Constants.SHARED_PREF_IMPORT_SETTINGS) - prefImportSettings!!.setOnPreferenceClickListener { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = "text/plain" - putExtra(DocumentsContract.EXTRA_INITIAL_URI, downloadUri) + val prefImportSettings = findPreference(Constants.SHARED_PREF_IMPORT_SETTINGS) + prefImportSettings!!.setOnPreferenceClickListener { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "text/plain" + putExtra(DocumentsContract.EXTRA_INITIAL_URI, downloadUri) + } + startActivityForResult(intent, IMPORT_PHONE_SETTINGS) + true } - startActivityForResult(intent, IMPORT_PHONE_SETTINGS) - true - } - val prefSaveMobileLogs = findPreference(Constants.SHARED_PREF_SAVE_MOBILE_LOGS) - prefSaveMobileLogs!!.setOnPreferenceClickListener { - SaveLogs(AppSource.PHONE_APP, downloadUri) - true - } + val prefSaveMobileLogs = findPreference(Constants.SHARED_PREF_SAVE_MOBILE_LOGS) + prefSaveMobileLogs!!.setOnPreferenceClickListener { + SaveLogs(AppSource.PHONE_APP, downloadUri) + true + } - val prefSaveWearLogs = findPreference(Constants.SHARED_PREF_SAVE_WEAR_LOGS) - prefSaveWearLogs!!.isEnabled = WearPhoneConnection.nodesConnected && !LogcatReceiver.isActive - prefSaveWearLogs.setOnPreferenceClickListener { - SaveLogs(AppSource.WEAR_APP, downloadUri) - true + val prefSaveWearLogs = findPreference(Constants.SHARED_PREF_SAVE_WEAR_LOGS) + prefSaveWearLogs!!.isEnabled = WearPhoneConnection.nodesConnected && !LogcatReceiver.isActive + prefSaveWearLogs.setOnPreferenceClickListener { + SaveLogs(AppSource.WEAR_APP, downloadUri) + true + } + } catch (exc: Exception) { + Log.e(LOG_ID, "initPreferences exception: " + exc.message.toString() ) } } override fun onResume() { - super.onResume() - InternalNotifier.addNotifier(requireContext(), this, mutableSetOf(NotifySource.CAPILITY_INFO)) + try { + super.onResume() + InternalNotifier.addNotifier(requireContext(), this, mutableSetOf(NotifySource.CAPILITY_INFO)) + } catch (exc: Exception) { + Log.e(LOG_ID, "onResume exception: " + exc.message.toString() ) + } } override fun onPause() { - super.onPause() - InternalNotifier.remNotifier(requireContext(), this) + try { + super.onPause() + InternalNotifier.remNotifier(requireContext(), this) + } catch (exc: Exception) { + Log.e(LOG_ID, "onPause exception: " + exc.message.toString() ) + } } private fun updateWearPrefs() { @@ -143,8 +155,12 @@ class ExportImportSettingsFragment: SettingsFragmentBase(R.xml.pref_export_impor } override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) { - if(dataSource == NotifySource.CAPILITY_INFO) { - updateWearPrefs() + try { + if(dataSource == NotifySource.CAPILITY_INFO) { + updateWearPrefs() + } + } catch (exc: Exception) { + Log.e(LOG_ID, "OnNotifyData exception: " + exc.message.toString() ) } } } \ No newline at end of file diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragment.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragment.kt index dd2135064..503e202e9 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragment.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragment.kt @@ -6,6 +6,7 @@ import androidx.preference.* import de.michelinside.glucodatahandler.BuildConfig import de.michelinside.glucodatahandler.R import de.michelinside.glucodatahandler.common.Constants +import de.michelinside.glucodatahandler.common.preferences.PreferenceHelper class SettingsFragment : PreferenceFragmentCompat() { @@ -17,6 +18,7 @@ class SettingsFragment : PreferenceFragmentCompat() { preferenceManager.sharedPreferencesName = Constants.SHARED_PREF_TAG setPreferencesFromResource(R.xml.preferences, rootKey) + PreferenceHelper.replaceSecondSummary(findPreference("pref_cat_android_auto")) if (BuildConfig.DEBUG) { val notifySwitch = findPreference(Constants.SHARED_PREF_DUMMY_VALUES) diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragmentBase.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragmentBase.kt index 2af47692e..ff5867013 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragmentBase.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragmentBase.kt @@ -21,9 +21,9 @@ import de.michelinside.glucodatahandler.android_auto.CarModeReceiver import de.michelinside.glucodatahandler.common.Constants import de.michelinside.glucodatahandler.common.GlucoDataService import de.michelinside.glucodatahandler.common.Intents -import de.michelinside.glucodatahandler.common.ReceiveData import de.michelinside.glucodatahandler.common.notifier.InternalNotifier import de.michelinside.glucodatahandler.common.notifier.NotifySource +import de.michelinside.glucodatahandler.common.preferences.PreferenceHelper import de.michelinside.glucodatahandler.common.utils.PackageUtils import kotlin.collections.HashMap import kotlin.collections.List @@ -76,10 +76,10 @@ abstract class SettingsFragmentBase(private val prefResId: Int) : SettingsFragme override fun onResume() { Log.d(LOG_ID, "onResume called") try { + super.onResume() preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) updateEnablePrefs.clear() update() - super.onResume() } catch (exc: Exception) { Log.e(LOG_ID, "onResume exception: " + exc.toString()) } @@ -88,8 +88,8 @@ abstract class SettingsFragmentBase(private val prefResId: Int) : SettingsFragme override fun onPause() { Log.d(LOG_ID, "onPause called") try { - preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) super.onPause() + preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) } catch (exc: Exception) { Log.e(LOG_ID, "onPause exception: " + exc.toString()) } @@ -310,6 +310,7 @@ class LockscreenSettingsFragment: SettingsFragmentBase(R.xml.pref_lockscreen) { } class NotificaitonSettingsFragment: SettingsFragmentBase(R.xml.pref_notification) {} + class WatchSettingsFragment: SettingsFragmentBase(R.xml.pref_watch) { override fun initPreferences() { Log.v(LOG_ID, "initPreferences called") @@ -325,15 +326,18 @@ class WatchSettingsFragment: SettingsFragmentBase(R.xml.pref_watch) { true } - val prefWatchDripLink = findPreference(Constants.SHARED_PREF_OPEN_WATCH_DRIP_LINK) - prefWatchDripLink!!.setOnPreferenceClickListener { - val browserIntent = Intent( - Intent.ACTION_VIEW, - Uri.parse(resources.getText(CR.string.watchdrip_link).toString()) - ) - startActivity(browserIntent) - true - } + PreferenceHelper.setLinkOnClick(findPreference(Constants.SHARED_PREF_OPEN_WATCH_DRIP_LINK), CR.string.watchdrip_link, requireContext()) + } +} + +class WatchFaceFragment: SettingsFragmentBase(R.xml.pref_watchfaces) { + override fun initPreferences() { + Log.v(LOG_ID, "initPreferences called") + + PreferenceHelper.setLinkOnClick(findPreference(Constants.SHARED_PREF_WATCHFACES_PUJIE), CR.string.playstore_pujie_watchfaces, requireContext()) + PreferenceHelper.setLinkOnClick(findPreference(Constants.SHARED_PREF_WATCHFACES_DMM), CR.string.playstore_dmm_watchfaces, requireContext()) + PreferenceHelper.setLinkOnClick(findPreference(Constants.SHARED_PREF_WATCHFACES_GDC), CR.string.playstore_gdc_watchfaces, requireContext()) + } } @@ -353,21 +357,19 @@ class GDASettingsFragment: SettingsFragmentBase(R.xml.pref_gda) { super.initPreferences() if (PackageUtils.isGlucoDataAutoAvailable(requireContext())) { val sendToGDA = findPreference(Constants.SHARED_PREF_SEND_TO_GLUCODATAAUTO) + PreferenceHelper.replaceSecondTitle(sendToGDA) + PreferenceHelper.replaceSecondSummary(sendToGDA) sendToGDA!!.isVisible = true val sendPrefToGDA = findPreference(Constants.SHARED_PREF_SEND_PREF_TO_GLUCODATAAUTO) + PreferenceHelper.replaceSecondSummary(sendPrefToGDA) sendPrefToGDA!!.isVisible = true } else { val no_gda_info = findPreference(Constants.SHARED_PREF_NO_GLUCODATAAUTO) if (no_gda_info != null) { + PreferenceHelper.replaceSecondTitle(no_gda_info) + PreferenceHelper.replaceSecondSummary(no_gda_info) no_gda_info.isVisible = true - no_gda_info.setOnPreferenceClickListener { - val browserIntent = Intent( - Intent.ACTION_VIEW, - Uri.parse(resources.getText(CR.string.glucodataauto_link).toString()) - ) - startActivity(browserIntent) - true - } + PreferenceHelper.setLinkOnClick(no_gda_info, CR.string.glucodataauto_link, requireContext()) } } } @@ -378,9 +380,7 @@ class GDASettingsFragment: SettingsFragmentBase(R.xml.pref_gda) { Constants.SHARED_PREF_SEND_TO_GLUCODATAAUTO, Constants.SHARED_PREF_SEND_PREF_TO_GLUCODATAAUTO -> { if(CarModeReceiver.AA_connected) { - val extras = ReceiveData.createExtras() - if (extras != null) - CarModeReceiver.sendToGlucoDataAuto(requireContext(), extras, true) + CarModeReceiver.sendToGlucoDataAuto(requireContext(), true) } } } diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceOfflineFragment.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceOfflineFragment.kt index 0eeeed7a8..5b3be6dd4 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceOfflineFragment.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceOfflineFragment.kt @@ -9,6 +9,7 @@ import de.michelinside.glucodatahandler.common.ui.Dialogs import de.michelinside.glucodatahandler.R import de.michelinside.glucodatahandler.common.R as CR import de.michelinside.glucodatahandler.common.Constants +import de.michelinside.glucodatahandler.common.preferences.PreferenceHelper class SourceOfflineFragment : SettingsFragmentCompatBase() { @@ -25,18 +26,8 @@ class SourceOfflineFragment : SettingsFragmentCompatBase() { setupLocalIobAction(findPreference(Constants.SHARED_PREF_SOURCE_JUGGLUCO_SET_NS_IOB_ACTION)) setupLocalIobAction(findPreference(Constants.SHARED_PREF_SOURCE_XDRIP_SET_NS_IOB_ACTION)) - val prefEselLink = findPreference(Constants.SHARED_PREF_EVERSENSE_ESEL_INFO) - if(prefEselLink != null) { - prefEselLink.setOnPreferenceClickListener { - val browserIntent = Intent( - Intent.ACTION_VIEW, - Uri.parse(resources.getText(CR.string.esel_link).toString()) - ) - startActivity(browserIntent) - true - } - } - + PreferenceHelper.setLinkOnClick(findPreference(Constants.SHARED_PREF_EVERSENSE_ESEL_INFO), CR.string.esel_link, requireContext()) + PreferenceHelper.setLinkOnClick(findPreference("source_juggluco_video"), CR.string.video_tutorial_juggluco, requireContext()) } catch (exc: Exception) { Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString()) } diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/VibratePatternPreferenceDialogFragmentCompat.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/VibratePatternPreferenceDialogFragmentCompat.kt index ed1078569..c233a019f 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/VibratePatternPreferenceDialogFragmentCompat.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/VibratePatternPreferenceDialogFragmentCompat.kt @@ -10,6 +10,7 @@ import androidx.preference.PreferenceDialogFragmentCompat import de.michelinside.glucodatahandler.R import de.michelinside.glucodatahandler.common.notification.VibratePattern import de.michelinside.glucodatahandler.common.notification.Vibrator +import de.michelinside.glucodatahandler.notification.AlarmNotification class VibratePatternPreferenceDialogFragmentCompat : PreferenceDialogFragmentCompat() { @@ -108,7 +109,9 @@ class VibratePatternPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom Log.v(LOG_ID, "Set currentPattern: $currentPattern") val pattern = VibratePattern.getByKey(currentPattern).pattern if(pattern != null) { - Vibrator.vibrate(pattern, -1, curAmplitude) + Vibrator.vibrate(pattern, -1, curAmplitude, AlarmNotification.useAlarmStream) + } else { + Vibrator.cancel() } } } catch (exc: Exception) { diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/tasker/WriteSettingConfigureActivity.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/tasker/WriteSettingConfigureActivity.kt index 9cb9e6e2e..da3f94327 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/tasker/WriteSettingConfigureActivity.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/tasker/WriteSettingConfigureActivity.kt @@ -6,7 +6,7 @@ import android.util.Log import androidx.appcompat.app.AppCompatActivity import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfig import com.joaomgcd.taskerpluginlibrary.input.TaskerInput -import de.michelinside.glucodatahandler.R +import de.michelinside.glucodatahandler.common.R as CR import de.michelinside.glucodatahandler.databinding.TaskerWriteSettingBinding @@ -39,7 +39,7 @@ class WriteSettingConfigureActivity : AppCompatActivity(), setContentView(binding.root) val supportedValues = resources - .getStringArray(R.array.tasker_supported_settings_values) + .getStringArray(CR.array.tasker_supported_settings_values) if (supportedValues.contains(key)) { binding.spinnerSetting.setSelection(supportedValues.asList().indexOf(key)) diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/tasker/WriteSettingRunner.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/tasker/WriteSettingRunner.kt index 3b26917fa..953b5c07f 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/tasker/WriteSettingRunner.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/tasker/WriteSettingRunner.kt @@ -7,7 +7,7 @@ import com.joaomgcd.taskerpluginlibrary.input.TaskerInput import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResult import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResultError import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResultSucess -import de.michelinside.glucodatahandler.R +import de.michelinside.glucodatahandler.common.R as CR import de.michelinside.glucodatahandler.common.Constants import de.michelinside.glucodatahandler.common.GlucoDataService import de.michelinside.glucodatahandler.common.notifier.NotifySource @@ -20,7 +20,7 @@ class WriteSettingRunner : TaskerPluginRunnerActionNoOutput() class WatchDripReceiver: BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -199,6 +202,7 @@ object WatchDrip: SharedPreferences.OnSharedPreferenceChangeListener, NotifierIn sendBroadcastToReceiver(context, it, bundle) } } + lastSendValuesTime = ReceiveData.time } else { Log.i(LOG_ID, "No receiver found for sending broadcast") } @@ -308,12 +312,26 @@ object WatchDrip: SharedPreferences.OnSharedPreferenceChangeListener, NotifierIn Constants.SHARED_PREF_WATCHDRIP -> { updateSettings(sharedPreferences!!, true) } + Constants.SHARED_PREF_SEND_TO_WATCH_INTERVAL -> { + sendInterval = sharedPreferences!!.getInt(Constants.SHARED_PREF_SEND_TO_WATCH_INTERVAL, 1) + } } } catch (exc: Exception) { Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.toString() + "\n" + exc.stackTraceToString() ) } } + private fun canSendBroadcast(dataSource: NotifySource): Boolean { + if(sendInterval > 1 && !ReceiveData.forceAlarm && ReceiveData.getAlarmType() != AlarmType.VERY_LOW && (dataSource == NotifySource.BROADCAST || dataSource == NotifySource.MESSAGECLIENT)) { + val elapsed = Utils.getElapsedTimeMinute(lastSendValuesTime, RoundingMode.HALF_UP) + if ( elapsed < sendInterval) { + Log.i(LOG_ID, "Ignore data because of interval $sendInterval - elapsed: $elapsed") + return false + } + } + return true + } + override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) { try { Log.v(LOG_ID, "OnNotifyData called for source " + dataSource) @@ -333,7 +351,8 @@ object WatchDrip: SharedPreferences.OnSharedPreferenceChangeListener, NotifierIn } } else -> { - sendBroadcast(context, BroadcastServiceAPI.CMD_UPDATE_BG) + if(canSendBroadcast(dataSource)) + sendBroadcast(context, BroadcastServiceAPI.CMD_UPDATE_BG) } } } catch (exc: Exception) { diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/FloatingWidget.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/FloatingWidget.kt index 985a454a1..82e9a613f 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/FloatingWidget.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/FloatingWidget.kt @@ -14,6 +14,8 @@ import android.util.TypedValue import android.view.* import android.view.View.* import android.widget.ImageView +import android.widget.TableLayout +import android.widget.TableRow import android.widget.TextView import de.michelinside.glucodatahandler.R import de.michelinside.glucodatahandler.common.Constants @@ -37,6 +39,7 @@ class FloatingWidget(val context: Context) : NotifierInterface, SharedPreference private lateinit var txtTime: TextView private lateinit var txtIob: TextView private lateinit var txtCob: TextView + private lateinit var column2: TableLayout private lateinit var sharedPref: SharedPreferences private lateinit var sharedInternalPref: SharedPreferences private val LOG_ID = "GDH.FloatingWidget" @@ -59,6 +62,7 @@ class FloatingWidget(val context: Context) : NotifierInterface, SharedPreference txtTime = floatingView.findViewById(R.id.timeText) txtIob = floatingView.findViewById(R.id.iobText) txtCob = floatingView.findViewById(R.id.cobText) + column2 = floatingView.findViewById(R.id.column2) //setting the layout parameters update() } catch (exc: Exception) { @@ -99,7 +103,7 @@ class FloatingWidget(val context: Context) : NotifierInterface, SharedPreference } private fun applyStyle() : Float { - var bgTextSize = 30f + var bgTextSize = 10f when(sharedPref.getString(Constants.SHARED_PREF_FLOATING_WIDGET_STYLE, Constants.WIDGET_STYLE_GLUCOSE_TREND_TIME_DELTA)) { Constants.WIDGET_STYLE_GLUCOSE_TREND_DELTA -> { txtTime.visibility = GONE @@ -123,7 +127,7 @@ class FloatingWidget(val context: Context) : NotifierInterface, SharedPreference txtCob.visibility = GONE } Constants.WIDGET_STYLE_GLUCOSE_TREND_TIME_DELTA_IOB_COB -> { - bgTextSize = 20f + bgTextSize = 10f txtTime.visibility = VISIBLE txtDelta.visibility = VISIBLE viewIcon.visibility = VISIBLE @@ -131,7 +135,7 @@ class FloatingWidget(val context: Context) : NotifierInterface, SharedPreference txtCob.visibility = VISIBLE } else -> { - bgTextSize = 20f + bgTextSize = 10f txtTime.visibility = VISIBLE txtDelta.visibility = VISIBLE viewIcon.visibility = VISIBLE @@ -152,20 +156,27 @@ class FloatingWidget(val context: Context) : NotifierInterface, SharedPreference } else { txtBgValue.paintFlags = 0 } - viewIcon.setImageIcon(BitmapUtils.getRateAsIcon()) + val resizeFactor = sharedPref.getInt(Constants.SHARED_PREF_FLOATING_WIDGET_SIZE, 5).toFloat() + val size = minOf(resizeFactor.toInt()*20, 200) + viewIcon.setImageIcon(BitmapUtils.getRateAsIcon(width = size, height = size)) viewIcon.contentDescription = ReceiveData.getRateAsText(context) txtDelta.text = "Δ ${ReceiveData.getDeltaAsString()}" txtTime.text = "🕒 ${ReceiveData.getElapsedTimeMinuteAsString(context)}" txtIob.text = "💉 ${ReceiveData.getIobAsString()}" txtCob.text = "🍔 ${ReceiveData.getCobAsString()}" - val resizeFactor = sharedPref.getInt(Constants.SHARED_PREF_FLOATING_WIDGET_SIZE, 3).toFloat() - txtBgValue.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize+resizeFactor*4f) - viewIcon.minimumWidth = Utils.dpToPx(32f+resizeFactor*4f, context) - txtDelta.setTextSize(TypedValue.COMPLEX_UNIT_SP, minOf(8f+ resizeFactor *2f, MAX_SIZE)) - txtTime.setTextSize(TypedValue.COMPLEX_UNIT_SP, minOf(8f+ resizeFactor *2f, MAX_SIZE)) - txtIob.setTextSize(TypedValue.COMPLEX_UNIT_SP, minOf(8f+ resizeFactor *2f, MAX_SIZE)) - txtCob.setTextSize(TypedValue.COMPLEX_UNIT_SP, minOf(8f+ resizeFactor *2f, MAX_SIZE)) + txtBgValue.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize+resizeFactor*5f) + viewIcon.minimumWidth = Utils.dpToPx(20f+resizeFactor*4f, context) + txtDelta.setTextSize(TypedValue.COMPLEX_UNIT_SP, minOf(6f+ resizeFactor *2f, MAX_SIZE)) + txtTime.setTextSize(TypedValue.COMPLEX_UNIT_SP, minOf(6f+ resizeFactor *2f, MAX_SIZE)) + txtIob.setTextSize(TypedValue.COMPLEX_UNIT_SP, minOf(6f+ resizeFactor *2f, MAX_SIZE)) + txtCob.setTextSize(TypedValue.COMPLEX_UNIT_SP, minOf(6f+ resizeFactor *2f, MAX_SIZE)) + + if(txtDelta.visibility == VISIBLE) { + val layout = TableRow.LayoutParams(TableLayout.LayoutParams.WRAP_CONTENT, TableLayout.LayoutParams.MATCH_PARENT) + layout.marginStart = Utils.spToPx(minOf(resizeFactor*3F, 15F), context) + column2.layoutParams = layout + } } private fun update() { diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/GlucoseBaseWidget.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/GlucoseBaseWidget.kt index 1002107fd..c15f385b2 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/GlucoseBaseWidget.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/GlucoseBaseWidget.kt @@ -204,7 +204,7 @@ abstract class GlucoseBaseWidget(private val type: WidgetType, remoteViews.setImageViewBitmap(R.id.glucose_trend, BitmapUtils.getGlucoseTrendBitmap(width = width, height = width)) remoteViews.setContentDescription(R.id.glucose_trend, ReceiveData.getAsText(context)) } else { - val size = maxOf(width, height) + val size = minOf(500, maxOf(width, height)) remoteViews.setImageViewBitmap( R.id.trendImage, BitmapUtils.getRateAsBitmap( width = size, diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt index f3f986472..2fb003a19 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt @@ -6,7 +6,6 @@ import android.content.SharedPreferences import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Paint -import android.graphics.drawable.BitmapDrawable import android.os.Bundle import android.util.Log import android.util.TypedValue @@ -16,6 +15,7 @@ import android.view.View.GONE import android.view.View.VISIBLE import android.widget.ImageView import android.widget.TextView +import androidx.core.graphics.drawable.toDrawable import de.michelinside.glucodatahandler.R import de.michelinside.glucodatahandler.common.Constants import de.michelinside.glucodatahandler.common.GlucoDataService @@ -115,11 +115,16 @@ object LockScreenWallpaper : NotifierInterface, SharedPreferences.OnSharedPrefer private fun setWallpaper(bitmap: Bitmap?, context: Context) { GlobalScope.launch { try { - Log.v(LOG_ID, "updateLockScreen called for bitmap $bitmap") val wallpaperManager = WallpaperManager.getInstance(context) - val wallpaper = if (bitmap != null) createWallpaper(bitmap, context) else null - wallpaperManager.setBitmap(wallpaper, null, false, WallpaperManager.FLAG_LOCK) - //wallpaper?.recycle() + if (bitmap != null) { + Log.i(LOG_ID, "Update lockscreen wallpaper") + val wallpaper = createWallpaper(bitmap, context) + wallpaperManager.setBitmap(wallpaper, null, false, WallpaperManager.FLAG_LOCK) + wallpaper!!.recycle() + } else { + Log.i(LOG_ID, "Remove lockscreen wallpaper") + wallpaperManager.clear() + } } catch (exc: Exception) { Log.e(LOG_ID, "updateLockScreen exception: " + exc.message.toString()) } @@ -134,7 +139,7 @@ object LockScreenWallpaper : NotifierInterface, SharedPreferences.OnSharedPrefer val screenDPI = BitmapUtils.getScreenDpi().toFloat() val wallpaper = Bitmap.createBitmap(screenWidth, screenHeigth, Bitmap.Config.ARGB_8888) val canvas = Canvas(wallpaper) - val drawable = BitmapDrawable(context.resources, bitmap) + val drawable = bitmap.toDrawable(context.resources) drawable.setBounds(0, 0, screenWidth, screenHeigth) val xOffset = ((screenWidth-bitmap.width)/2F) //*1.2F-(screenDPI*0.3F) val yOffset = max(0F, ((screenHeigth-bitmap.height)*yPos/100F)) //-(screenDPI*0.3F)) diff --git a/mobile/src/main/res/drawable-nodpi/restart_phone.png b/mobile/src/main/res/drawable-nodpi/restart_phone.png new file mode 100644 index 000000000..cb80f2fed Binary files /dev/null and b/mobile/src/main/res/drawable-nodpi/restart_phone.png differ diff --git a/mobile/src/main/res/layout-land/activity_main.xml b/mobile/src/main/res/layout-land/activity_main.xml index 02c71a52f..f9cbf0bd4 100644 --- a/mobile/src/main/res/layout-land/activity_main.xml +++ b/mobile/src/main/res/layout-land/activity_main.xml @@ -143,23 +143,6 @@ android:layout_marginTop="20dp" android:text="@string/menu_sources" /> - - - @@ -184,41 +167,24 @@ android:orientation="vertical" android:gravity="center|top"> - - - - - + android:divider="?android:attr/dividerHorizontal" + android:showDividers="middle"> + + + + + + android:text="@string/alarm_header"/> diff --git a/mobile/src/main/res/layout/activity_main.xml b/mobile/src/main/res/layout/activity_main.xml index f5b0140b0..e339784e6 100644 --- a/mobile/src/main/res/layout/activity_main.xml +++ b/mobile/src/main/res/layout/activity_main.xml @@ -132,50 +132,33 @@ android:layout_marginTop="20dp" android:text="@string/menu_sources" /> + + + + + - - - - - + android:divider="?android:attr/dividerHorizontal" + android:showDividers="middle"> + + + + + + android:text="@string/alarm_header"/> diff --git a/mobile/src/main/res/layout/alarm_notification.xml b/mobile/src/main/res/layout/alarm_notification.xml index 264ecc3aa..cbc9a8679 100644 --- a/mobile/src/main/res/layout/alarm_notification.xml +++ b/mobile/src/main/res/layout/alarm_notification.xml @@ -50,20 +50,21 @@ android:textColor="@color/text_color" tools:ignore="HardcodedText,SpUsage" /> + -