refactor import/export to create DaggerAppCompatActivityWithResult
This commit is contained in:
parent
3f18f94c79
commit
65a39d91be
45 changed files with 491 additions and 499 deletions
|
@ -5,11 +5,13 @@ import javax.inject.Inject
|
|||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class Config @Inject constructor(): ConfigInterface{
|
||||
class Config @Inject constructor() : ConfigInterface {
|
||||
|
||||
override val SUPPORTEDNSVERSION = 1002 // 0.10.00
|
||||
override val APS = BuildConfig.FLAVOR == "full"
|
||||
override val NSCLIENT = BuildConfig.FLAVOR == "nsclient" || BuildConfig.FLAVOR == "nsclient2"
|
||||
override val PUMPCONTROL = BuildConfig.FLAVOR == "pumpcontrol"
|
||||
override val PUMPDRIVERS = BuildConfig.FLAVOR == "full" || BuildConfig.FLAVOR == "pumpcontrol"
|
||||
override val FLAVOR = BuildConfig.FLAVOR
|
||||
override val VERSION_NAME = BuildConfig.VERSION_NAME
|
||||
}
|
|
@ -47,8 +47,6 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
|||
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
|
||||
import info.nightscout.androidaps.plugins.constraints.signatureVerifier.SignatureVerifierPlugin
|
||||
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFileContract
|
||||
import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus
|
||||
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
|
||||
import info.nightscout.androidaps.setupwizard.SetupWizardActivity
|
||||
|
@ -92,18 +90,11 @@ class MainActivity : NoSplashAppCompatActivity() {
|
|||
@Inject lateinit var constraintChecker: ConstraintChecker
|
||||
@Inject lateinit var signatureVerifierPlugin: SignatureVerifierPlugin
|
||||
@Inject lateinit var config: Config
|
||||
@Inject lateinit var importExportPrefs: ImportExportPrefs
|
||||
|
||||
private lateinit var actionBarDrawerToggle: ActionBarDrawerToggle
|
||||
private var pluginPreferencesMenuItem: MenuItem? = null
|
||||
private var menu: Menu? = null
|
||||
|
||||
val callForPrefFile = registerForActivityResult(PrefsFileContract()) {
|
||||
it?.let {
|
||||
importExportPrefs.importSharedPreferences(this, it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Iconify.with(FontAwesomeModule())
|
||||
|
@ -203,10 +194,9 @@ class MainActivity : NoSplashAppCompatActivity() {
|
|||
if (p.isEnabled() && p.hasFragment() && !p.isFragmentVisible() && !p.pluginDescription.neverVisible) {
|
||||
val menuItem = menu.add(p.name)
|
||||
menuItem.isCheckable = true
|
||||
if(p.menuIcon != -1) {
|
||||
if (p.menuIcon != -1) {
|
||||
menuItem.setIcon(p.menuIcon)
|
||||
} else
|
||||
{
|
||||
} else {
|
||||
menuItem.setIcon(R.drawable.ic_settings)
|
||||
}
|
||||
menuItem.setOnMenuItemClickListener {
|
||||
|
@ -283,7 +273,7 @@ class MainActivity : NoSplashAppCompatActivity() {
|
|||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
this.menu = menu
|
||||
this.menu = menu
|
||||
menuInflater.inflate(R.menu.menu_main, menu)
|
||||
pluginPreferencesMenuItem = menu.findItem(R.id.nav_plugin_preferences)
|
||||
setPluginPreferenceMenuName()
|
||||
|
|
|
@ -5,29 +5,20 @@ import android.content.Intent
|
|||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import dagger.android.support.DaggerAppCompatActivity
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.interfaces.PluginBase
|
||||
import info.nightscout.androidaps.plugins.configBuilder.PluginStore
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFileContract
|
||||
import info.nightscout.androidaps.utils.locale.LocaleHelper
|
||||
import info.nightscout.androidaps.utils.protection.ProtectionCheck
|
||||
import javax.inject.Inject
|
||||
|
||||
class SingleFragmentActivity : DaggerAppCompatActivity() {
|
||||
class SingleFragmentActivity : DaggerAppCompatActivityWithResult() {
|
||||
|
||||
@Inject lateinit var pluginStore: PluginStore
|
||||
@Inject lateinit var protectionCheck: ProtectionCheck
|
||||
@Inject lateinit var importExportPrefs: ImportExportPrefs
|
||||
|
||||
private var plugin: PluginBase? = null
|
||||
|
||||
val callForPrefFile = registerForActivityResult(PrefsFileContract()) {
|
||||
it?.let {
|
||||
importExportPrefs.importSharedPreferences(this, it)
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_single_fragment)
|
||||
|
|
|
@ -6,7 +6,6 @@ import info.nightscout.androidaps.MainActivity
|
|||
import info.nightscout.androidaps.activities.*
|
||||
import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.activities.LogSettingActivity
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.activities.PrefImportListActivity
|
||||
import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansLoginActivity
|
||||
import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity
|
||||
import info.nightscout.androidaps.plugins.general.smsCommunicator.activities.SmsCommunicatorOtpActivity
|
||||
|
@ -40,7 +39,6 @@ abstract class ActivitiesModule {
|
|||
@ContributesAndroidInjector abstract fun contributesStatsActivity(): StatsActivity
|
||||
@ContributesAndroidInjector abstract fun contributesSurveyActivity(): SurveyActivity
|
||||
@ContributesAndroidInjector abstract fun contributesDefaultProfileActivity(): ProfileHelperActivity
|
||||
@ContributesAndroidInjector abstract fun contributesPrefImportListActivity(): PrefImportListActivity
|
||||
@ContributesAndroidInjector abstract fun contributesOpenHumansLoginActivity(): OpenHumansLoginActivity
|
||||
|
||||
}
|
|
@ -12,6 +12,7 @@ import info.nightscout.androidaps.db.DatabaseHelperProvider
|
|||
import info.nightscout.androidaps.interfaces.*
|
||||
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
|
||||
import info.nightscout.androidaps.plugins.configBuilder.PluginStore
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs
|
||||
import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue
|
||||
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
|
||||
import info.nightscout.androidaps.queue.CommandQueue
|
||||
|
@ -58,5 +59,6 @@ open class AppModule {
|
|||
@Binds fun bindDatabaseHelperInterface(databaseHelperProvider: DatabaseHelperProvider): DatabaseHelperInterface
|
||||
@Binds fun bindUploadQueueInterface(uploadQueue: UploadQueue): UploadQueueInterface
|
||||
@Binds fun bindNotificationHolderInterface(notificationHolder: NotificationHolder): NotificationHolderInterface
|
||||
@Binds fun bindImportExportPrefsInterface(importExportPrefs: ImportExportPrefs): ImportExportPrefsInterface
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package info.nightscout.androidaps.dependencyInjection
|
|||
|
||||
import dagger.Module
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.PrefFileListProvider
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.ClassicPrefsFormat
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.EncryptedPrefsFormat
|
||||
|
@ -13,7 +12,6 @@ import info.nightscout.androidaps.utils.CryptoUtil
|
|||
abstract class PreferencesModule {
|
||||
|
||||
@ContributesAndroidInjector abstract fun cryptoUtilInjector(): CryptoUtil
|
||||
@ContributesAndroidInjector abstract fun importExportPrefsInjector(): ImportExportPrefs
|
||||
@ContributesAndroidInjector abstract fun encryptedPrefsFormatInjector(): EncryptedPrefsFormat
|
||||
@ContributesAndroidInjector abstract fun classicPrefsFormatInjector(): ClassicPrefsFormat
|
||||
@ContributesAndroidInjector abstract fun prefImportListProviderInjector(): PrefFileListProvider
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package info.nightscout.androidaps.plugins.constraints.objectives.objectives;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.text.util.Linkify;
|
||||
|
@ -185,9 +184,9 @@ public abstract class Objective {
|
|||
int days = (int) Math.floor((double) duration / T.days(1).msecs());
|
||||
int hours = (int) Math.floor((double) duration / T.hours(1).msecs());
|
||||
int minutes = (int) Math.floor((double) duration / T.mins(1).msecs());
|
||||
if (days > 0) return resourceHelper.gq(R.plurals.objective_days, days, days);
|
||||
else if (hours > 0) return resourceHelper.gq(R.plurals.objective_hours, hours, hours);
|
||||
else return resourceHelper.gq(R.plurals.objective_minutes, minutes, minutes);
|
||||
if (days > 0) return resourceHelper.gq(R.plurals.days, days, days);
|
||||
else if (hours > 0) return resourceHelper.gq(R.plurals.hours, hours, hours);
|
||||
else return resourceHelper.gq(R.plurals.minutes, minutes, minutes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,16 +12,15 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import info.nightscout.androidaps.BuildConfig
|
||||
import info.nightscout.androidaps.MainActivity
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.activities.DaggerAppCompatActivityWithResult
|
||||
import info.nightscout.androidaps.activities.PreferencesActivity
|
||||
import info.nightscout.androidaps.activities.SingleFragmentActivity
|
||||
import info.nightscout.androidaps.events.EventAppExit
|
||||
import info.nightscout.androidaps.interfaces.ImportExportPrefsInterface
|
||||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
import info.nightscout.androidaps.logging.LTag
|
||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.*
|
||||
import info.nightscout.androidaps.setupwizard.SetupWizardActivity
|
||||
import info.nightscout.androidaps.utils.AndroidPermission
|
||||
import info.nightscout.androidaps.utils.DateUtil
|
||||
import info.nightscout.androidaps.utils.ToastUtils
|
||||
|
@ -40,6 +39,7 @@ import java.io.IOException
|
|||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* Created by mike on 03.07.2016.
|
||||
|
@ -57,19 +57,17 @@ class ImportExportPrefs @Inject constructor(
|
|||
private val classicPrefsFormat: ClassicPrefsFormat,
|
||||
private val encryptedPrefsFormat: EncryptedPrefsFormat,
|
||||
private val prefFileList: PrefFileListProvider
|
||||
) {
|
||||
) : ImportExportPrefsInterface {
|
||||
|
||||
val TAG = LTag.CORE
|
||||
|
||||
fun prefsFileExists(): Boolean {
|
||||
override fun prefsFileExists(): Boolean {
|
||||
return prefFileList.listPreferenceFiles().size > 0
|
||||
}
|
||||
|
||||
fun exportSharedPreferences(f: Fragment) {
|
||||
override fun exportSharedPreferences(f: Fragment) {
|
||||
f.activity?.let { exportSharedPreferences(it) }
|
||||
}
|
||||
|
||||
fun verifyStoragePermissions(fragment: Fragment, onGranted: Runnable) {
|
||||
override fun verifyStoragePermissions(fragment: Fragment, onGranted: Runnable) {
|
||||
fragment.context?.let { ctx ->
|
||||
val permission = ContextCompat.checkSelfPermission(ctx,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
|
@ -119,8 +117,7 @@ class ImportExportPrefs @Inject constructor(
|
|||
|
||||
// name we detect from OS
|
||||
val systemName = n1 ?: n2 ?: n3 ?: n4 ?: n5 ?: n6 ?: defaultPatientName
|
||||
val name = if (patientName.isNotEmpty() && patientName != defaultPatientName) patientName else systemName
|
||||
return name
|
||||
return if (patientName.isNotEmpty() && patientName != defaultPatientName) patientName else systemName
|
||||
}
|
||||
|
||||
private fun prefsEncryptionIsDisabled() =
|
||||
|
@ -134,6 +131,7 @@ class ImportExportPrefs @Inject constructor(
|
|||
})
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun askForEncryptionPass(activity: FragmentActivity, @StringRes canceledMsg: Int, @StringRes passwordName: Int, @StringRes passwordExplanation: Int?,
|
||||
@StringRes passwordWarning: Int?, then: ((password: String) -> Unit)) {
|
||||
passwordCheck.queryAnyPassword(activity, passwordName, R.string.key_master_password, passwordExplanation, passwordWarning, { password ->
|
||||
|
@ -143,6 +141,7 @@ class ImportExportPrefs @Inject constructor(
|
|||
})
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun askForMasterPassIfNeeded(activity: FragmentActivity, @StringRes canceledMsg: Int, then: ((password: String) -> Unit)) {
|
||||
if (prefsEncryptionIsDisabled()) {
|
||||
then("")
|
||||
|
@ -239,49 +238,45 @@ class ImportExportPrefs @Inject constructor(
|
|||
ToastUtils.okToast(activity, resourceHelper.gs(R.string.exported))
|
||||
} catch (e: FileNotFoundException) {
|
||||
ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + newFile)
|
||||
log.error(TAG, "Unhandled exception", e)
|
||||
log.error(LTag.CORE, "Unhandled exception", e)
|
||||
} catch (e: IOException) {
|
||||
ToastUtils.errorToast(activity, e.message)
|
||||
log.error(TAG, "Unhandled exception", e)
|
||||
log.error(LTag.CORE, "Unhandled exception", e)
|
||||
} catch (e: PrefFileNotFoundError) {
|
||||
ToastUtils.Long.errorToast(activity, resourceHelper.gs(R.string.preferences_export_canceled)
|
||||
+ "\n\n" + resourceHelper.gs(R.string.filenotfound)
|
||||
+ ": " + e.message
|
||||
+ "\n\n" + resourceHelper.gs(R.string.needstoragepermission))
|
||||
log.error(TAG, "File system exception", e)
|
||||
log.error(LTag.CORE, "File system exception", e)
|
||||
} catch (e: PrefIOError) {
|
||||
ToastUtils.Long.errorToast(activity, resourceHelper.gs(R.string.preferences_export_canceled)
|
||||
+ "\n\n" + resourceHelper.gs(R.string.needstoragepermission)
|
||||
+ ": " + e.message)
|
||||
log.error(TAG, "File system exception", e)
|
||||
log.error(LTag.CORE, "File system exception", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun importSharedPreferences(fragment: Fragment) {
|
||||
override fun importSharedPreferences(fragment: Fragment) {
|
||||
fragment.activity?.let { fragmentAct ->
|
||||
importSharedPreferences(fragmentAct)
|
||||
}
|
||||
}
|
||||
|
||||
fun importSharedPreferences(activity: FragmentActivity) {
|
||||
override fun importSharedPreferences(activity: FragmentActivity) {
|
||||
|
||||
try {
|
||||
if (activity is SingleFragmentActivity)
|
||||
activity.callForPrefFile.launch(null)
|
||||
if (activity is MainActivity)
|
||||
activity.callForPrefFile.launch(null)
|
||||
if (activity is SetupWizardActivity)
|
||||
if (activity is DaggerAppCompatActivityWithResult)
|
||||
activity.callForPrefFile.launch(null)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// this exception happens on some early implementations of ActivityResult contracts
|
||||
// when registered and called for the second time
|
||||
ToastUtils.errorToast(activity, resourceHelper.gs(R.string.goto_main_try_again))
|
||||
log.error(TAG, "Internal android framework exception", e)
|
||||
log.error(LTag.CORE, "Internal android framework exception", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun importSharedPreferences(activity: FragmentActivity, importFile: PrefsFile) {
|
||||
override fun importSharedPreferences(activity: FragmentActivity, importFile: PrefsFile) {
|
||||
|
||||
askToConfirmImport(activity, importFile) { password ->
|
||||
|
||||
|
@ -301,7 +296,7 @@ class ImportExportPrefs @Inject constructor(
|
|||
promptForDecryptionPasswordIfNeeded(activity, prefsAttempted, importOkAttempted, format, importFile) { prefs, importOk ->
|
||||
|
||||
// if at end we allow to import preferences
|
||||
val importPossible = (importOk || buildHelper.isEngineeringMode()) && (prefs.values.size > 0)
|
||||
val importPossible = (importOk || buildHelper.isEngineeringMode()) && (prefs.values.isNotEmpty())
|
||||
|
||||
PrefImportSummaryDialog.showSummary(activity, importOk, importPossible, prefs, {
|
||||
if (importPossible) {
|
||||
|
@ -325,9 +320,9 @@ class ImportExportPrefs @Inject constructor(
|
|||
|
||||
} catch (e: PrefFileNotFoundError) {
|
||||
ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + importFile)
|
||||
log.error(TAG, "Unhandled exception", e)
|
||||
log.error(LTag.CORE, "Unhandled exception", e)
|
||||
} catch (e: PrefIOError) {
|
||||
log.error(TAG, "Unhandled exception", e)
|
||||
log.error(LTag.CORE, "Unhandled exception", e)
|
||||
ToastUtils.errorToast(activity, e.message)
|
||||
}
|
||||
}
|
||||
|
@ -346,13 +341,13 @@ class ImportExportPrefs @Inject constructor(
|
|||
private fun restartAppAfterImport(context: Context) {
|
||||
sp.putBoolean(R.string.key_setupwizard_processed, true)
|
||||
show(context, resourceHelper.gs(R.string.setting_imported), resourceHelper.gs(R.string.restartingapp), Runnable {
|
||||
log.debug(TAG, "Exiting")
|
||||
log.debug(LTag.CORE, "Exiting")
|
||||
rxBus.send(EventAppExit())
|
||||
if (context is AppCompatActivity) {
|
||||
context.finish()
|
||||
}
|
||||
System.runFinalization()
|
||||
System.exit(0)
|
||||
exitProcess(0)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import android.view.ViewGroup
|
|||
import dagger.android.support.DaggerFragment
|
||||
import info.nightscout.androidaps.MainApp
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.interfaces.ImportExportPrefsInterface
|
||||
import info.nightscout.androidaps.plugins.general.food.FoodPlugin
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.activities.LogSettingActivity
|
||||
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
|
||||
|
@ -23,7 +24,7 @@ class MaintenanceFragment : DaggerFragment() {
|
|||
@Inject lateinit var resourceHelper: ResourceHelper
|
||||
@Inject lateinit var treatmentsPlugin: TreatmentsPlugin
|
||||
@Inject lateinit var foodPlugin: FoodPlugin
|
||||
@Inject lateinit var importExportPrefs: ImportExportPrefs
|
||||
@Inject lateinit var importExportPrefs: ImportExportPrefsInterface
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.maintenance_fragment, container, false)
|
||||
|
|
|
@ -11,6 +11,7 @@ import info.nightscout.androidaps.dialogs.ProfileSwitchDialog
|
|||
import info.nightscout.androidaps.events.EventPumpStatusChanged
|
||||
import info.nightscout.androidaps.interfaces.ActivePluginProvider
|
||||
import info.nightscout.androidaps.interfaces.CommandQueueProvider
|
||||
import info.nightscout.androidaps.interfaces.ImportExportPrefsInterface
|
||||
import info.nightscout.androidaps.interfaces.PluginType
|
||||
import info.nightscout.androidaps.interfaces.ProfileFunction
|
||||
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
|
||||
|
@ -18,7 +19,6 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
|||
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
|
||||
import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesFragment
|
||||
import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs
|
||||
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin
|
||||
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus
|
||||
import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService
|
||||
|
@ -55,7 +55,7 @@ class SWDefinition @Inject constructor(
|
|||
private val loopPlugin: LoopPlugin,
|
||||
private val nsClientPlugin: NSClientPlugin,
|
||||
private val nsProfilePlugin: NSProfilePlugin,
|
||||
private val importExportPrefs: ImportExportPrefs,
|
||||
private val importExportPrefs: ImportExportPrefsInterface,
|
||||
private val androidPermission: AndroidPermission,
|
||||
private val cryptoUtil: CryptoUtil,
|
||||
private val config: Config
|
||||
|
|
|
@ -14,8 +14,6 @@ import info.nightscout.androidaps.events.EventProfileNeedsUpdate
|
|||
import info.nightscout.androidaps.events.EventProfileStoreChanged
|
||||
import info.nightscout.androidaps.events.EventPumpStatusChanged
|
||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFileContract
|
||||
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus
|
||||
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
|
||||
import info.nightscout.androidaps.plugins.pump.common.events.EventRileyLinkDeviceStatusChange
|
||||
|
@ -44,7 +42,6 @@ class SetupWizardActivity : NoSplashAppCompatActivity() {
|
|||
@Inject lateinit var resourceHelper: ResourceHelper
|
||||
@Inject lateinit var sp: SP
|
||||
@Inject lateinit var fabricPrivacy: FabricPrivacy
|
||||
@Inject lateinit var importExportPrefs: ImportExportPrefs
|
||||
|
||||
private val disposable = CompositeDisposable()
|
||||
private lateinit var screens: List<SWScreen>
|
||||
|
@ -52,12 +49,6 @@ class SetupWizardActivity : NoSplashAppCompatActivity() {
|
|||
|
||||
private val intentMessage = "WIZZARDPAGE"
|
||||
|
||||
val callForPrefFile = registerForActivityResult(PrefsFileContract()) {
|
||||
it?.let {
|
||||
importExportPrefs.importSharedPreferences(this, it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
update(applicationContext)
|
||||
|
@ -213,7 +204,7 @@ class SetupWizardActivity : NoSplashAppCompatActivity() {
|
|||
if (permissions.isNotEmpty()) {
|
||||
if (ActivityCompat.checkSelfPermission(this, permissions[0]) == PackageManager.PERMISSION_GRANTED) {
|
||||
when (requestCode) {
|
||||
AndroidPermission.CASE_STORAGE -> //show dialog after permission is granted
|
||||
AndroidPermission.CASE_STORAGE -> //show dialog after permission is granted
|
||||
show(this, resourceHelper.gs(R.string.permission), resourceHelper.gs(R.string.alert_dialog_storage_permission_text))
|
||||
|
||||
AndroidPermission.CASE_LOCATION, AndroidPermission.CASE_SMS, AndroidPermission.CASE_BATTERY -> {
|
||||
|
|
|
@ -64,10 +64,6 @@
|
|||
|
||||
<color name="splashBackground">#2E2E2E</color>
|
||||
|
||||
<color name="metadataOk">#77dd77</color>
|
||||
<color name="metadataTextWarning">#FF8C00</color>
|
||||
<color name="metadataTextError">#FF5555</color>
|
||||
|
||||
<color name="importListFileName">#FFFFFF</color>
|
||||
<color name="importListAdditionalInfo">#BBBBBB</color>
|
||||
|
||||
|
|
|
@ -66,16 +66,4 @@
|
|||
<string name="failedretrievetime">Failed retrieve time</string>
|
||||
<string name="requirementnotmet">Objective requirements not met</string>
|
||||
|
||||
<plurals name="objective_days">
|
||||
<item quantity="one">%1$d day</item>
|
||||
<item quantity="other">%1$d days</item>
|
||||
</plurals>
|
||||
<plurals name="objective_hours">
|
||||
<item quantity="one">%1$d hour</item>
|
||||
<item quantity="other">%1$d hours</item>
|
||||
</plurals>
|
||||
<plurals name="objective_minutes">
|
||||
<item quantity="one">%1$d minute</item>
|
||||
<item quantity="other">%1$d minutes</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
|
|
@ -204,8 +204,6 @@
|
|||
<string name="different_password_used">This file was exported and encrypted with different master password. Provide old master password to decrypt file.</string>
|
||||
<string name="master_password_will_be_replaced">As a result of successful import current master password WILL BE REPLACED with that old master password!</string>
|
||||
|
||||
<string name="preferences_import_list_title">Select file to import</string>
|
||||
|
||||
<string name="check_preferences_before_import">Please check preferences before importing:</string>
|
||||
<string name="check_preferences_cannot_import">Preferences cannot be imported!</string>
|
||||
<string name="check_preferences_dangerous_import">Preferences should not be imported!</string>
|
||||
|
@ -214,45 +212,6 @@
|
|||
<string name="check_preferences_import_btn">Import</string>
|
||||
<string name="check_preferences_import_anyway_btn">Import anyway (DANGEROUS!)</string>
|
||||
|
||||
<string name="metadata_warning_different_flavour">Preferences were created with different variant of AAPS (%1$s) while you have: %2$s.\n\nSome settings may be missing or invalid - after importing please check and update your preferences.</string>
|
||||
<string name="metadata_warning_different_device">Preferences were created on a different device. It is OK if you are importing from older/different phone, but make sure imported preferences are correct!</string>
|
||||
<string name="metadata_warning_outdated_format">You are using the outdated legacy format from old versions of AAPS, which is not secure! Only use it as a last resort, if you do not have an export in current, JSON format.</string>
|
||||
<string name="metadata_warning_old_export">Imported preferences are already %1$s days old! Maybe you have more up-to-date preferences or you choose the wrong file? Remember to export preferences regularly!</string>
|
||||
<string name="metadata_warning_date_format">Invalid date-time format!</string>
|
||||
<string name="metadata_warning_different_version">Preferences from different minor version of application. It is OK if you are importing after upgrade, but check after import if preferences are still correct!</string>
|
||||
<string name="metadata_urgent_different_version">Preferences from different major version of application. Major versions differ significantly and may have incompatible preferences! Make sure after import that preferences are still correct!</string>
|
||||
|
||||
<string name="metadata_label_format">File format</string>
|
||||
<string name="metadata_label_created_at">Created at</string>
|
||||
<string name="metadata_label_aaps_version">AAPS Version</string>
|
||||
<string name="metadata_label_aaps_flavour">Build Variant</string>
|
||||
<string name="metadata_label_device_name">Exporting device patient name</string>
|
||||
<string name="metadata_label_device_model">Exporting device model</string>
|
||||
<string name="metadata_label_encryption">File encryption</string>
|
||||
|
||||
<string name="metadata_format_old">Old export format</string>
|
||||
<string name="metadata_format_new">New encrypted format</string>
|
||||
<string name="metadata_format_debug">New debug format (unencrypted)</string>
|
||||
<string name="metadata_format_other">Unknown export format</string>
|
||||
|
||||
<string name="prefdecrypt_settings_tampered">Settings file tampered</string>
|
||||
<string name="prefdecrypt_settings_secure">Settings file is secure</string>
|
||||
<string name="prefdecrypt_settings_unencrypted">Using not secure, unencrypted settings format</string>
|
||||
<string name="prefdecrypt_wrong_json">JSON format error, missing required field (format, content, metadata or security)</string>
|
||||
<string name="prefdecrypt_wrong_password">Decryption error, the given password cannot decrypt the file</string>
|
||||
|
||||
<string name="prefdecrypt_issue_missing_file_hash">File checksum (hash) missing, cannot verify the authenticity of settings!</string>
|
||||
<string name="prefdecrypt_issue_modified">File was modified after export!</string>
|
||||
<string name="prefdecrypt_issue_parsing">Decryption error, parsing preferences failed!</string>
|
||||
<string name="prefdecrypt_issue_wrong_pass">Decryption error, the provided password is invalid or settings file was modified! It may happen that the imported file was exported with a different Master password.</string>
|
||||
<string name="prefdecrypt_issue_wrong_format">Missing encryption configuration, settings format is invalid!</string>
|
||||
<string name="prefdecrypt_issue_wrong_algorithm">Unsupported or not specified encryption algorithm!</string>
|
||||
|
||||
<string name="exported_ago" comment="at placeholder we add pluralized number of hours/minutes">exported %1$s ago</string>
|
||||
<string name="exported_at" comment="at placeholder we add export date">exported at %1$s</string>
|
||||
<string name="exported_less_than_hour_ago">exported less than hour ago</string>
|
||||
<string name="in_directory" comment="placeholder is for exported file path">in directory: %1$s</string>
|
||||
|
||||
<string name="end_user_license_agreement">End User License Agreement</string>
|
||||
<string name="end_user_license_agreement_text">MUST NOT BE USED TO MAKE MEDICAL DECISIONS. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.</string>
|
||||
<string name="end_user_license_agreement_i_understand">I UNDERSTAND AND AGREE</string>
|
||||
|
@ -977,7 +936,6 @@
|
|||
<string name="nav_logsettings">Log settings</string>
|
||||
<string name="resettodefaults">Reset to defaults</string>
|
||||
<string name="nsmalfunction">NSClient malfunction. Consider NS and NSClient restart.</string>
|
||||
<string name="versionavailable">Version %1$s available</string>
|
||||
<string name="time_offset">Time offset</string>
|
||||
<string name="key_aps_mode" translatable="false">aps_mode</string>
|
||||
<string name="setupwizard_preferred_aps_mode">Preferred APS mode</string>
|
||||
|
@ -1158,12 +1116,6 @@
|
|||
<string name="copy_short">COPY</string>
|
||||
<string name="addnew">Add new</string>
|
||||
<string name="versionChecker">Version Checker</string>
|
||||
<string name="key_last_time_this_version_detected" translatable="false">last_time_this_version_detected</string>
|
||||
<string name="key_last_versionchecker_warning" translatable="false">last_versionchecker_waring</string>
|
||||
<string name="key_last_versionchecker_plugin_warning" translatable="false">last_versionchecker_plugin_waring</string>
|
||||
<string name="key_last_revoked_certs_check" translatable="false">last_revoked_certs_check</string>
|
||||
<string name="signature_verifier">Signature verifier</string>
|
||||
<string name="running_invalid_version">We have detected that you are running an invalid version. Loop disabled!</string>
|
||||
|
||||
<string name="old_version">old version</string>
|
||||
<string name="very_old_version">very old version</string>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package info.nightscout.androidaps.activities
|
||||
|
||||
import dagger.android.support.DaggerAppCompatActivity
|
||||
import info.nightscout.androidaps.interfaces.ImportExportPrefsInterface
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFileContract
|
||||
import javax.inject.Inject
|
||||
|
||||
open class DaggerAppCompatActivityWithResult : DaggerAppCompatActivity() {
|
||||
|
||||
@Inject lateinit var importExportPrefs: ImportExportPrefsInterface
|
||||
|
||||
val callForPrefFile = registerForActivityResult(PrefsFileContract()) {
|
||||
it?.let {
|
||||
importExportPrefs.importSharedPreferences(this, it)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,12 +2,10 @@ package info.nightscout.androidaps.activities
|
|||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import dagger.android.support.DaggerAppCompatActivity
|
||||
import info.nightscout.androidaps.core.R
|
||||
import info.nightscout.androidaps.utils.locale.LocaleHelper
|
||||
|
||||
//@Suppress("registered")
|
||||
open class NoSplashAppCompatActivity : DaggerAppCompatActivity() {
|
||||
open class NoSplashAppCompatActivity : DaggerAppCompatActivityWithResult() {
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
|
@ -9,11 +9,13 @@ import info.nightscout.androidaps.dialogs.BolusProgressDialog
|
|||
import info.nightscout.androidaps.dialogs.ErrorDialog
|
||||
import info.nightscout.androidaps.dialogs.NtpProgressDialog
|
||||
import info.nightscout.androidaps.dialogs.ProfileViewerDialog
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.activities.PrefImportListActivity
|
||||
|
||||
@Module
|
||||
@Suppress("unused")
|
||||
abstract class CoreFragmentsModule {
|
||||
|
||||
@ContributesAndroidInjector abstract fun contributesPrefImportListActivity(): PrefImportListActivity
|
||||
@ContributesAndroidInjector abstract fun contributesTDDStatsActivity(): TDDStatsActivity
|
||||
@ContributesAndroidInjector abstract fun contributeBolusProgressHelperActivity(): BolusProgressHelperActivity
|
||||
@ContributesAndroidInjector abstract fun contributeErrorHelperActivity(): ErrorHelperActivity
|
||||
|
|
|
@ -6,4 +6,6 @@ interface ConfigInterface {
|
|||
val NSCLIENT: Boolean
|
||||
val PUMPCONTROL: Boolean
|
||||
val PUMPDRIVERS: Boolean
|
||||
val FLAVOR: String
|
||||
val VERSION_NAME: String
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package info.nightscout.androidaps.interfaces
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFile
|
||||
|
||||
interface ImportExportPrefsInterface {
|
||||
|
||||
fun importSharedPreferences(activity: FragmentActivity, importFile: PrefsFile)
|
||||
fun importSharedPreferences(activity: FragmentActivity)
|
||||
fun importSharedPreferences(fragment: Fragment)
|
||||
fun prefsFileExists(): Boolean
|
||||
fun verifyStoragePermissions(fragment: Fragment, onGranted: Runnable)
|
||||
fun exportSharedPreferences(f: Fragment)
|
||||
}
|
|
@ -2,9 +2,8 @@ package info.nightscout.androidaps.plugins.constraints.versionChecker
|
|||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import info.nightscout.androidaps.BuildConfig
|
||||
import info.nightscout.androidaps.MainApp
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.core.R
|
||||
import info.nightscout.androidaps.interfaces.ConfigInterface
|
||||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
import info.nightscout.androidaps.logging.LTag
|
||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
||||
|
@ -24,6 +23,7 @@ class VersionCheckerUtils @Inject constructor(
|
|||
val sp: SP,
|
||||
val resourceHelper: ResourceHelper,
|
||||
val rxBus: RxBusWrapper,
|
||||
private val config: ConfigInterface,
|
||||
val context: Context
|
||||
) {
|
||||
|
||||
|
@ -50,7 +50,7 @@ class VersionCheckerUtils @Inject constructor(
|
|||
Thread {
|
||||
try {
|
||||
val version: String? = findVersion(URL("https://raw.githubusercontent.com/nightscout/AndroidAPS/master/app/build.gradle").readText())
|
||||
compareWithCurrentVersion(version, BuildConfig.VERSION_NAME)
|
||||
compareWithCurrentVersion(version, config.VERSION_NAME)
|
||||
} catch (e: IOException) {
|
||||
aapsLogger.error(LTag.CORE, "Github master version check error: $e")
|
||||
}
|
||||
|
@ -130,6 +130,7 @@ class VersionCheckerUtils @Inject constructor(
|
|||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val CHECK_EVERY = TimeUnit.DAYS.toMillis(1)
|
||||
private val WARN_EVERY = TimeUnit.DAYS.toMillis(1)
|
||||
}
|
||||
|
@ -148,7 +149,7 @@ fun findVersion(file: String?): String? {
|
|||
fun String.versionStrip() = this.mapNotNull {
|
||||
when (it) {
|
||||
in '0'..'9' -> it
|
||||
'.' -> it
|
||||
'.' -> it
|
||||
else -> null
|
||||
}
|
||||
}.joinToString(separator = "")
|
|
@ -1,220 +1,178 @@
|
|||
package info.nightscout.androidaps.plugins.general.maintenance
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.Parcelable
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import info.nightscout.androidaps.BuildConfig
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.activities.PrefImportListActivity
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.*
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import info.nightscout.androidaps.utils.storage.Storage
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.parcel.RawValue
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.Days
|
||||
import org.joda.time.Hours
|
||||
import org.joda.time.LocalDateTime
|
||||
import org.joda.time.format.DateTimeFormat
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
enum class PrefsImportDir {
|
||||
ROOT_DIR,
|
||||
AAPS_DIR
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class PrefsFile(
|
||||
val name: String,
|
||||
val file: File,
|
||||
val baseDir: File,
|
||||
val dirKind: PrefsImportDir,
|
||||
val handler: PrefsFormatsHandler,
|
||||
|
||||
// metadata here is used only for list display
|
||||
val metadata: @RawValue Map<PrefsMetadataKey, PrefMetadata>
|
||||
) : Parcelable
|
||||
|
||||
class PrefsFileContract : ActivityResultContract<Void, PrefsFile>() {
|
||||
|
||||
companion object {
|
||||
|
||||
const val OUTPUT_PARAM = "prefs_file"
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): PrefsFile? {
|
||||
return when (resultCode) {
|
||||
FragmentActivity.RESULT_OK -> intent?.getParcelableExtra(OUTPUT_PARAM)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun createIntent(context: Context, input: Void?): Intent {
|
||||
return Intent(context, PrefImportListActivity::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
fun getCurrentDeviceModelString() =
|
||||
Build.MANUFACTURER + " " + Build.MODEL + " (" + Build.DEVICE + ")"
|
||||
|
||||
@Singleton
|
||||
class PrefFileListProvider @Inject constructor(
|
||||
private val resourceHelper: ResourceHelper,
|
||||
private val classicPrefsFormat: ClassicPrefsFormat,
|
||||
private val encryptedPrefsFormat: EncryptedPrefsFormat,
|
||||
private val storage: Storage,
|
||||
private val versionCheckerUtils: VersionCheckerUtils
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
private val path = File(Environment.getExternalStorageDirectory().toString())
|
||||
private val aapsPath = File(path, "AAPS" + File.separator + "preferences")
|
||||
private const val IMPORT_AGE_NOT_YET_OLD_DAYS = 60
|
||||
}
|
||||
|
||||
/**
|
||||
* This function tries to list possible preference files from main SDCard root dir and AAPS/preferences dir
|
||||
* and tries to do quick assessment for preferences format plausibility.
|
||||
* It does NOT load full metadata or is 100% accurate - it tries to do QUICK detection, based on:
|
||||
* - file name and extension
|
||||
* - predicted file contents
|
||||
*/
|
||||
fun listPreferenceFiles(loadMetadata: Boolean = false): MutableList<PrefsFile> {
|
||||
val prefFiles = mutableListOf<PrefsFile>()
|
||||
|
||||
// searching rood dir for legacy files
|
||||
path.walk().maxDepth(1).filter { it.isFile && (it.name.endsWith(".json") || it.name.contains("Preferences")) }.forEach {
|
||||
val contents = storage.getFileContents(it)
|
||||
val detectedNew = encryptedPrefsFormat.isPreferencesFile(it, contents)
|
||||
val detectedOld = !detectedNew && classicPrefsFormat.isPreferencesFile(it, contents)
|
||||
if (detectedNew || detectedOld) {
|
||||
val formatHandler = if (detectedNew) PrefsFormatsHandler.ENCRYPTED else PrefsFormatsHandler.CLASSIC
|
||||
prefFiles.add(PrefsFile(it.name, it, path, PrefsImportDir.ROOT_DIR, formatHandler, metadataFor(loadMetadata, formatHandler, contents)))
|
||||
}
|
||||
}
|
||||
|
||||
// searching dedicated dir, only for new JSON format
|
||||
aapsPath.walk().filter { it.isFile && it.name.endsWith(".json") }.forEach {
|
||||
val contents = storage.getFileContents(it)
|
||||
if (encryptedPrefsFormat.isPreferencesFile(it, contents)) {
|
||||
prefFiles.add(PrefsFile(it.name, it, aapsPath, PrefsImportDir.AAPS_DIR, PrefsFormatsHandler.ENCRYPTED, metadataFor(loadMetadata, PrefsFormatsHandler.ENCRYPTED, contents)))
|
||||
}
|
||||
}
|
||||
|
||||
// we sort only if we have metadata to be used for that
|
||||
if (loadMetadata) {
|
||||
prefFiles.sortWith(
|
||||
compareByDescending<PrefsFile> { it.handler }
|
||||
.thenBy { it.metadata[PrefsMetadataKey.AAPS_FLAVOUR]?.status }
|
||||
.thenByDescending { it.metadata[PrefsMetadataKey.CREATED_AT]?.value }
|
||||
)
|
||||
}
|
||||
|
||||
return prefFiles
|
||||
}
|
||||
|
||||
private fun metadataFor(loadMetadata: Boolean, formatHandler: PrefsFormatsHandler, contents: String): PrefMetadataMap {
|
||||
if (!loadMetadata) {
|
||||
return mapOf()
|
||||
}
|
||||
return checkMetadata(when (formatHandler) {
|
||||
PrefsFormatsHandler.CLASSIC -> classicPrefsFormat.loadMetadata(contents)
|
||||
PrefsFormatsHandler.ENCRYPTED -> encryptedPrefsFormat.loadMetadata(contents)
|
||||
})
|
||||
}
|
||||
|
||||
fun legacyFile(): File {
|
||||
return File(path, resourceHelper.gs(R.string.app_name) + "Preferences")
|
||||
}
|
||||
|
||||
fun ensureExportDirExists() {
|
||||
if (!aapsPath.exists()) {
|
||||
aapsPath.mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
fun newExportFile(): File {
|
||||
val timeLocal = LocalDateTime.now().toString(DateTimeFormat.forPattern("yyyy-MM-dd'_'HHmmss"))
|
||||
return File(aapsPath, timeLocal + "_" + BuildConfig.FLAVOR + ".json")
|
||||
}
|
||||
|
||||
// check metadata for known issues, change their status and add info with explanations
|
||||
fun checkMetadata(metadata: Map<PrefsMetadataKey, PrefMetadata>): Map<PrefsMetadataKey, PrefMetadata> {
|
||||
val meta = metadata.toMutableMap()
|
||||
|
||||
meta[PrefsMetadataKey.AAPS_FLAVOUR]?.let { flavour ->
|
||||
val flavourOfPrefs = flavour.value
|
||||
if (flavour.value != BuildConfig.FLAVOR) {
|
||||
flavour.status = PrefsStatus.WARN
|
||||
flavour.info = resourceHelper.gs(R.string.metadata_warning_different_flavour, flavourOfPrefs, BuildConfig.FLAVOR)
|
||||
}
|
||||
}
|
||||
|
||||
meta[PrefsMetadataKey.DEVICE_MODEL]?.let { model ->
|
||||
if (model.value != getCurrentDeviceModelString()) {
|
||||
model.status = PrefsStatus.WARN
|
||||
model.info = resourceHelper.gs(R.string.metadata_warning_different_device)
|
||||
}
|
||||
}
|
||||
|
||||
meta[PrefsMetadataKey.CREATED_AT]?.let { createdAt ->
|
||||
try {
|
||||
val date1 = DateTime.parse(createdAt.value)
|
||||
val date2 = DateTime.now()
|
||||
|
||||
val daysOld = Days.daysBetween(date1.toLocalDate(), date2.toLocalDate()).days
|
||||
|
||||
if (daysOld > IMPORT_AGE_NOT_YET_OLD_DAYS) {
|
||||
createdAt.status = PrefsStatus.WARN
|
||||
createdAt.info = resourceHelper.gs(R.string.metadata_warning_old_export, daysOld.toString())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
createdAt.status = PrefsStatus.WARN
|
||||
createdAt.info = resourceHelper.gs(R.string.metadata_warning_date_format)
|
||||
}
|
||||
}
|
||||
|
||||
meta[PrefsMetadataKey.AAPS_VERSION]?.let { version ->
|
||||
val currentAppVer = versionCheckerUtils.versionDigits(BuildConfig.VERSION_NAME)
|
||||
val metadataVer = versionCheckerUtils.versionDigits(version.value)
|
||||
|
||||
if ((currentAppVer.size >= 2) && (metadataVer.size >= 2) && (Math.abs(currentAppVer[1] - metadataVer[1]) > 1)) {
|
||||
version.status = PrefsStatus.WARN
|
||||
version.info = resourceHelper.gs(R.string.metadata_warning_different_version)
|
||||
}
|
||||
|
||||
if ((currentAppVer.isNotEmpty()) && (metadataVer.isNotEmpty()) && (currentAppVer[0] != metadataVer[0])) {
|
||||
version.status = PrefsStatus.WARN
|
||||
version.info = resourceHelper.gs(R.string.metadata_urgent_different_version)
|
||||
}
|
||||
}
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
fun formatExportedAgo(utcTime: String): String {
|
||||
val refTime = DateTime.now()
|
||||
val itTime = DateTime.parse(utcTime)
|
||||
val days = Days.daysBetween(itTime, refTime).days
|
||||
val hours = Hours.hoursBetween(itTime, refTime).hours
|
||||
|
||||
return if (hours == 0) {
|
||||
resourceHelper.gs(R.string.exported_less_than_hour_ago)
|
||||
} else if ((hours < 24) && (hours > 0)) {
|
||||
resourceHelper.gs(R.string.exported_ago, resourceHelper.gq(R.plurals.objective_hours, hours, hours))
|
||||
} else if ((days < IMPORT_AGE_NOT_YET_OLD_DAYS) && (days > 0)) {
|
||||
resourceHelper.gs(R.string.exported_ago, resourceHelper.gq(R.plurals.objective_days, days, days))
|
||||
} else {
|
||||
resourceHelper.gs(R.string.exported_at, utcTime.substring(0, 10))
|
||||
}
|
||||
}
|
||||
|
||||
package info.nightscout.androidaps.plugins.general.maintenance
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import info.nightscout.androidaps.core.R
|
||||
import info.nightscout.androidaps.interfaces.ConfigInterface
|
||||
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.*
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import info.nightscout.androidaps.utils.storage.Storage
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.Days
|
||||
import org.joda.time.Hours
|
||||
import org.joda.time.LocalDateTime
|
||||
import org.joda.time.format.DateTimeFormat
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.math.abs
|
||||
|
||||
fun getCurrentDeviceModelString() =
|
||||
Build.MANUFACTURER + " " + Build.MODEL + " (" + Build.DEVICE + ")"
|
||||
|
||||
@Singleton
|
||||
class PrefFileListProvider @Inject constructor(
|
||||
private val resourceHelper: ResourceHelper,
|
||||
private val config: ConfigInterface,
|
||||
private val classicPrefsFormat: ClassicPrefsFormat,
|
||||
private val encryptedPrefsFormat: EncryptedPrefsFormat,
|
||||
private val storage: Storage,
|
||||
private val versionCheckerUtils: VersionCheckerUtils
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
private val path = File(Environment.getExternalStorageDirectory().toString())
|
||||
private val aapsPath = File(path, "AAPS" + File.separator + "preferences")
|
||||
private const val IMPORT_AGE_NOT_YET_OLD_DAYS = 60
|
||||
}
|
||||
|
||||
/**
|
||||
* This function tries to list possible preference files from main SDCard root dir and AAPS/preferences dir
|
||||
* and tries to do quick assessment for preferences format plausibility.
|
||||
* It does NOT load full metadata or is 100% accurate - it tries to do QUICK detection, based on:
|
||||
* - file name and extension
|
||||
* - predicted file contents
|
||||
*/
|
||||
fun listPreferenceFiles(loadMetadata: Boolean = false): MutableList<PrefsFile> {
|
||||
val prefFiles = mutableListOf<PrefsFile>()
|
||||
|
||||
// searching rood dir for legacy files
|
||||
path.walk().maxDepth(1).filter { it.isFile && (it.name.endsWith(".json") || it.name.contains("Preferences")) }.forEach {
|
||||
val contents = storage.getFileContents(it)
|
||||
val detectedNew = encryptedPrefsFormat.isPreferencesFile(it, contents)
|
||||
val detectedOld = !detectedNew && classicPrefsFormat.isPreferencesFile(it, contents)
|
||||
if (detectedNew || detectedOld) {
|
||||
val formatHandler = if (detectedNew) PrefsFormatsHandler.ENCRYPTED else PrefsFormatsHandler.CLASSIC
|
||||
prefFiles.add(PrefsFile(it.name, it, path, PrefsImportDir.ROOT_DIR, formatHandler, metadataFor(loadMetadata, formatHandler, contents)))
|
||||
}
|
||||
}
|
||||
|
||||
// searching dedicated dir, only for new JSON format
|
||||
aapsPath.walk().filter { it.isFile && it.name.endsWith(".json") }.forEach {
|
||||
val contents = storage.getFileContents(it)
|
||||
if (encryptedPrefsFormat.isPreferencesFile(it, contents)) {
|
||||
prefFiles.add(PrefsFile(it.name, it, aapsPath, PrefsImportDir.AAPS_DIR, PrefsFormatsHandler.ENCRYPTED, metadataFor(loadMetadata, PrefsFormatsHandler.ENCRYPTED, contents)))
|
||||
}
|
||||
}
|
||||
|
||||
// we sort only if we have metadata to be used for that
|
||||
if (loadMetadata) {
|
||||
prefFiles.sortWith(
|
||||
compareByDescending<PrefsFile> { it.handler }
|
||||
.thenBy { it.metadata[PrefsMetadataKey.AAPS_FLAVOUR]?.status }
|
||||
.thenByDescending { it.metadata[PrefsMetadataKey.CREATED_AT]?.value }
|
||||
)
|
||||
}
|
||||
|
||||
return prefFiles
|
||||
}
|
||||
|
||||
private fun metadataFor(loadMetadata: Boolean, formatHandler: PrefsFormatsHandler, contents: String): PrefMetadataMap {
|
||||
if (!loadMetadata) {
|
||||
return mapOf()
|
||||
}
|
||||
return checkMetadata(when (formatHandler) {
|
||||
PrefsFormatsHandler.CLASSIC -> classicPrefsFormat.loadMetadata(contents)
|
||||
PrefsFormatsHandler.ENCRYPTED -> encryptedPrefsFormat.loadMetadata(contents)
|
||||
})
|
||||
}
|
||||
|
||||
fun legacyFile(): File {
|
||||
return File(path, resourceHelper.gs(R.string.app_name) + "Preferences")
|
||||
}
|
||||
|
||||
fun ensureExportDirExists() {
|
||||
if (!aapsPath.exists()) {
|
||||
aapsPath.mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
fun newExportFile(): File {
|
||||
val timeLocal = LocalDateTime.now().toString(DateTimeFormat.forPattern("yyyy-MM-dd'_'HHmmss"))
|
||||
return File(aapsPath, timeLocal + "_" + config.FLAVOR + ".json")
|
||||
}
|
||||
|
||||
// check metadata for known issues, change their status and add info with explanations
|
||||
fun checkMetadata(metadata: Map<PrefsMetadataKey, PrefMetadata>): Map<PrefsMetadataKey, PrefMetadata> {
|
||||
val meta = metadata.toMutableMap()
|
||||
|
||||
meta[PrefsMetadataKey.AAPS_FLAVOUR]?.let { flavour ->
|
||||
val flavourOfPrefs = flavour.value
|
||||
if (flavour.value != config.FLAVOR) {
|
||||
flavour.status = PrefsStatus.WARN
|
||||
flavour.info = resourceHelper.gs(R.string.metadata_warning_different_flavour, flavourOfPrefs, config.FLAVOR)
|
||||
}
|
||||
}
|
||||
|
||||
meta[PrefsMetadataKey.DEVICE_MODEL]?.let { model ->
|
||||
if (model.value != getCurrentDeviceModelString()) {
|
||||
model.status = PrefsStatus.WARN
|
||||
model.info = resourceHelper.gs(R.string.metadata_warning_different_device)
|
||||
}
|
||||
}
|
||||
|
||||
meta[PrefsMetadataKey.CREATED_AT]?.let { createdAt ->
|
||||
try {
|
||||
val date1 = DateTime.parse(createdAt.value)
|
||||
val date2 = DateTime.now()
|
||||
|
||||
val daysOld = Days.daysBetween(date1.toLocalDate(), date2.toLocalDate()).days
|
||||
|
||||
if (daysOld > IMPORT_AGE_NOT_YET_OLD_DAYS) {
|
||||
createdAt.status = PrefsStatus.WARN
|
||||
createdAt.info = resourceHelper.gs(R.string.metadata_warning_old_export, daysOld.toString())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
createdAt.status = PrefsStatus.WARN
|
||||
createdAt.info = resourceHelper.gs(R.string.metadata_warning_date_format)
|
||||
}
|
||||
}
|
||||
|
||||
meta[PrefsMetadataKey.AAPS_VERSION]?.let { version ->
|
||||
val currentAppVer = versionCheckerUtils.versionDigits(config.VERSION_NAME)
|
||||
val metadataVer = versionCheckerUtils.versionDigits(version.value)
|
||||
|
||||
if ((currentAppVer.size >= 2) && (metadataVer.size >= 2) && (abs(currentAppVer[1] - metadataVer[1]) > 1)) {
|
||||
version.status = PrefsStatus.WARN
|
||||
version.info = resourceHelper.gs(R.string.metadata_warning_different_version)
|
||||
}
|
||||
|
||||
if ((currentAppVer.isNotEmpty()) && (metadataVer.isNotEmpty()) && (currentAppVer[0] != metadataVer[0])) {
|
||||
version.status = PrefsStatus.WARN
|
||||
version.info = resourceHelper.gs(R.string.metadata_urgent_different_version)
|
||||
}
|
||||
}
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
fun formatExportedAgo(utcTime: String): String {
|
||||
val refTime = DateTime.now()
|
||||
val itTime = DateTime.parse(utcTime)
|
||||
val days = Days.daysBetween(itTime, refTime).days
|
||||
val hours = Hours.hoursBetween(itTime, refTime).hours
|
||||
|
||||
return if (hours == 0) {
|
||||
resourceHelper.gs(R.string.exported_less_than_hour_ago)
|
||||
} else if ((hours < 24) && (hours > 0)) {
|
||||
resourceHelper.gs(R.string.exported_ago, resourceHelper.gq(R.plurals.hours, hours, hours))
|
||||
} else if ((days < IMPORT_AGE_NOT_YET_OLD_DAYS) && (days > 0)) {
|
||||
resourceHelper.gs(R.string.exported_ago, resourceHelper.gq(R.plurals.days, days, days))
|
||||
} else {
|
||||
resourceHelper.gs(R.string.exported_at, utcTime.substring(0, 10))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package info.nightscout.androidaps.plugins.general.maintenance
|
||||
|
||||
import android.os.Parcelable
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefMetadata
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsMetadataKey
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.parcel.RawValue
|
||||
import java.io.File
|
||||
|
||||
@Parcelize
|
||||
data class PrefsFile(
|
||||
val name: String,
|
||||
val file: File,
|
||||
val baseDir: File,
|
||||
val dirKind: PrefsImportDir,
|
||||
val handler: PrefsFormatsHandler,
|
||||
|
||||
// metadata here is used only for list display
|
||||
val metadata: @RawValue Map<PrefsMetadataKey, PrefMetadata>
|
||||
) : Parcelable
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package info.nightscout.androidaps.plugins.general.maintenance
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.activities.PrefImportListActivity
|
||||
|
||||
class PrefsFileContract : ActivityResultContract<Void, PrefsFile>() {
|
||||
|
||||
companion object {
|
||||
|
||||
const val OUTPUT_PARAM = "prefs_file"
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): PrefsFile? {
|
||||
return when (resultCode) {
|
||||
FragmentActivity.RESULT_OK -> intent?.getParcelableExtra(OUTPUT_PARAM)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun createIntent(context: Context, input: Void?): Intent {
|
||||
return Intent(context, PrefImportListActivity::class.java)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package info.nightscout.androidaps.plugins.general.maintenance
|
||||
|
||||
enum class PrefsFormatsHandler {
|
||||
CLASSIC,
|
||||
ENCRYPTED
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package info.nightscout.androidaps.plugins.general.maintenance
|
||||
|
||||
enum class PrefsImportDir {
|
||||
ROOT_DIR,
|
||||
AAPS_DIR
|
||||
}
|
|
@ -12,11 +12,11 @@ import androidx.fragment.app.FragmentActivity
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dagger.android.support.DaggerAppCompatActivity
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.core.R
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.PrefFileListProvider
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFile
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFileContract
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsFormatsHandler
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFormatsHandler
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsMetadataKey
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsStatus
|
||||
import info.nightscout.androidaps.utils.locale.LocaleHelper
|
||||
|
@ -46,6 +46,7 @@ class PrefImportListActivity : DaggerAppCompatActivity() {
|
|||
inner class RecyclerViewAdapter internal constructor(private var prefFileList: List<PrefsFile>) : RecyclerView.Adapter<RecyclerViewAdapter.PrefFileViewHolder>() {
|
||||
|
||||
inner class PrefFileViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
var fileName: TextView = itemView.findViewById(R.id.filelist_name)
|
||||
var fileDir: TextView = itemView.findViewById(R.id.filelist_dir)
|
||||
var metaDateTime: TextView = itemView.findViewById(R.id.meta_date_time)
|
|
@ -1,7 +1,7 @@
|
|||
package info.nightscout.androidaps.plugins.general.maintenance.formats
|
||||
|
||||
import info.nightscout.androidaps.Constants
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.core.R
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import info.nightscout.androidaps.utils.storage.Storage
|
||||
import java.io.File
|
||||
|
@ -17,6 +17,7 @@ class ClassicPrefsFormat @Inject constructor(
|
|||
) : PrefsFormat {
|
||||
|
||||
companion object {
|
||||
|
||||
val FORMAT_KEY = "aaps_old"
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package info.nightscout.androidaps.plugins.general.maintenance.formats
|
||||
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.core.R
|
||||
import info.nightscout.androidaps.utils.CryptoUtil
|
||||
import info.nightscout.androidaps.utils.extensions.hexStringToByteArray
|
||||
import info.nightscout.androidaps.utils.extensions.toHex
|
||||
|
@ -23,6 +23,7 @@ class EncryptedPrefsFormat @Inject constructor(
|
|||
) : PrefsFormat {
|
||||
|
||||
companion object {
|
||||
|
||||
val FORMAT_KEY_ENC = "aaps_encrypted"
|
||||
val FORMAT_KEY_NOENC = "aaps_structured"
|
||||
|
|
@ -4,7 +4,7 @@ import android.content.Context
|
|||
import android.os.Parcelable
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.core.R
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import java.io.File
|
||||
|
||||
|
@ -74,11 +74,6 @@ enum class PrefsStatus(@DrawableRes val icon: Int) {
|
|||
DISABLED(R.drawable.ic_meta_error)
|
||||
}
|
||||
|
||||
enum class PrefsFormatsHandler {
|
||||
CLASSIC,
|
||||
ENCRYPTED
|
||||
}
|
||||
|
||||
class PrefFileNotFoundError(message: String) : Exception(message)
|
||||
class PrefIOError(message: String) : Exception(message)
|
||||
class PrefFormatError(message: String) : Exception(message)
|
|
@ -1,17 +1,17 @@
|
|||
package info.nightscout.androidaps.utils.storage
|
||||
|
||||
import java.io.File
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FileStorage : Storage {
|
||||
|
||||
override fun getFileContents(file: File): String {
|
||||
return file.readText()
|
||||
}
|
||||
|
||||
override fun putFileContents(file: File, contents: String) {
|
||||
file.writeText(contents)
|
||||
}
|
||||
|
||||
package info.nightscout.androidaps.utils.storage
|
||||
|
||||
import java.io.File
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FileStorage : Storage {
|
||||
|
||||
override fun getFileContents(file: File): String {
|
||||
return file.readText()
|
||||
}
|
||||
|
||||
override fun putFileContents(file: File, contents: String) {
|
||||
file.writeText(contents)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
package info.nightscout.androidaps.utils.storage
|
||||
|
||||
import java.io.File
|
||||
|
||||
// This may seems unnecessary abstraction - but it will simplify testing
|
||||
interface Storage {
|
||||
|
||||
fun getFileContents(file: File) : String
|
||||
fun putFileContents(file: File, contents: String)
|
||||
|
||||
}
|
||||
package info.nightscout.androidaps.utils.storage
|
||||
|
||||
import java.io.File
|
||||
|
||||
// This may seems unnecessary abstraction - but it will simplify testing
|
||||
interface Storage {
|
||||
|
||||
fun getFileContents(file: File): String
|
||||
fun putFileContents(file: File, contents: String)
|
||||
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M15,13H16.5V15.82L18.94,17.23L18.19,18.53L15,16.69V13M19,8H5V19H9.67C9.24,18.09 9,17.07 9,16A7,7 0 0,1 16,9C17.07,9 18.09,9.24 19,9.67V8M5,21C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H6V1H8V3H16V1H18V3H19A2,2 0 0,1 21,5V11.1C22.24,12.36 23,14.09 23,16A7,7 0 0,1 16,23C14.09,23 12.36,22.24 11.1,21H5M16,11.15A4.85,4.85 0 0,0 11.15,16C11.15,18.68 13.32,20.85 16,20.85A4.85,4.85 0 0,0 20.85,16C20.85,13.32 18.68,11.15 16,11.15Z" />
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M15,13H16.5V15.82L18.94,17.23L18.19,18.53L15,16.69V13M19,8H5V19H9.67C9.24,18.09 9,17.07 9,16A7,7 0 0,1 16,9C17.07,9 18.09,9.24 19,9.67V8M5,21C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H6V1H8V3H16V1H18V3H19A2,2 0 0,1 21,5V11.1C22.24,12.36 23,14.09 23,16A7,7 0 0,1 16,23C14.09,23 12.36,22.24 11.1,21H5M16,11.15A4.85,4.85 0 0,0 11.15,16C11.15,18.68 13.32,20.85 16,20.85A4.85,4.85 0 0,0 20.85,16C20.85,13.32 18.68,11.15 16,11.15Z" />
|
||||
</vector>
|
|
@ -1,9 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M21,11C21,16.55 17.16,21.74 12,23C6.84,21.74 3,16.55 3,11V5L12,1L21,5V11M12,21C15.75,20 19,15.54 19,11.22V6.3L12,3.18L5,6.3V11.22C5,15.54 8.25,20 12,21M14.8,11V9.5C14.8,8.1 13.4,7 12,7C10.6,7 9.2,8.1 9.2,9.5V11C8.6,11 8,11.6 8,12.2V15.7C8,16.4 8.6,17 9.2,17H14.7C15.4,17 16,16.4 16,15.8V12.3C16,11.6 15.4,11 14.8,11M13.5,11H10.5V9.5C10.5,8.7 11.2,8.2 12,8.2C12.8,8.2 13.5,8.7 13.5,9.5V11Z" />
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M21,11C21,16.55 17.16,21.74 12,23C6.84,21.74 3,16.55 3,11V5L12,1L21,5V11M12,21C15.75,20 19,15.54 19,11.22V6.3L12,3.18L5,6.3V11.22C5,15.54 8.25,20 12,21M14.8,11V9.5C14.8,8.1 13.4,7 12,7C10.6,7 9.2,8.1 9.2,9.5V11C8.6,11 8,11.6 8,12.2V15.7C8,16.4 8.6,17 9.2,17H14.7C15.4,17 16,16.4 16,15.8V12.3C16,11.6 15.4,11 14.8,11M13.5,11H10.5V9.5C10.5,8.7 11.2,8.2 12,8.2C12.8,8.2 13.5,8.7 13.5,9.5V11Z" />
|
||||
</vector>
|
|
@ -1,10 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/metadataTextError"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M8.27,3L3,8.27V15.73L8.27,21H15.73L21,15.73V8.27L15.73,3M8.41,7L12,10.59L15.59,7L17,8.41L13.41,12L17,15.59L15.59,17L12,13.41L8.41,17L7,15.59L10.59,12L7,8.41" />
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/metadataTextError"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M8.27,3L3,8.27V15.73L8.27,21H15.73L21,15.73V8.27L15.73,3M8.41,7L12,10.59L15.59,7L17,8.41L13.41,12L17,15.59L15.59,17L12,13.41L8.41,17L7,15.59L10.59,12L7,8.41" />
|
||||
</vector>
|
|
@ -1,9 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,10.84 21.79,9.69 21.39,8.61L19.79,10.21C19.93,10.8 20,11.4 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4C12.6,4 13.2,4.07 13.79,4.21L15.4,2.6C14.31,2.21 13.16,2 12,2M19,2L15,6V7.5L12.45,10.05C12.3,10 12.15,10 12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12C14,11.85 14,11.7 13.95,11.55L16.5,9H18L22,5H19V2M12,6A6,6 0 0,0 6,12A6,6 0 0,0 12,18A6,6 0 0,0 18,12H16A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8V6Z" />
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,10.84 21.79,9.69 21.39,8.61L19.79,10.21C19.93,10.8 20,11.4 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4C12.6,4 13.2,4.07 13.79,4.21L15.4,2.6C14.31,2.21 13.16,2 12,2M19,2L15,6V7.5L12.45,10.05C12.3,10 12.15,10 12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12C14,11.85 14,11.7 13.95,11.55L16.5,9H18L22,5H19V2M12,6A6,6 0 0,0 6,12A6,6 0 0,0 12,18A6,6 0 0,0 18,12H16A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8V6Z" />
|
||||
</vector>
|
|
@ -1,9 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M6 2C4.89 2 4 2.9 4 4V20C4 21.11 4.89 22 6 22H12V20H6V4H13V9H18V12H20V8L14 2M18 14C17.87 14 17.76 14.09 17.74 14.21L17.55 15.53C17.25 15.66 16.96 15.82 16.7 16L15.46 15.5C15.35 15.5 15.22 15.5 15.15 15.63L14.15 17.36C14.09 17.47 14.11 17.6 14.21 17.68L15.27 18.5C15.25 18.67 15.24 18.83 15.24 19C15.24 19.17 15.25 19.33 15.27 19.5L14.21 20.32C14.12 20.4 14.09 20.53 14.15 20.64L15.15 22.37C15.21 22.5 15.34 22.5 15.46 22.5L16.7 22C16.96 22.18 17.24 22.35 17.55 22.47L17.74 23.79C17.76 23.91 17.86 24 18 24H20C20.11 24 20.22 23.91 20.24 23.79L20.43 22.47C20.73 22.34 21 22.18 21.27 22L22.5 22.5C22.63 22.5 22.76 22.5 22.83 22.37L23.83 20.64C23.89 20.53 23.86 20.4 23.77 20.32L22.7 19.5C22.72 19.33 22.74 19.17 22.74 19C22.74 18.83 22.73 18.67 22.7 18.5L23.76 17.68C23.85 17.6 23.88 17.47 23.82 17.36L22.82 15.63C22.76 15.5 22.63 15.5 22.5 15.5L21.27 16C21 15.82 20.73 15.65 20.42 15.53L20.23 14.21C20.22 14.09 20.11 14 20 14M19 17.5C19.83 17.5 20.5 18.17 20.5 19C20.5 19.83 19.83 20.5 19 20.5C18.16 20.5 17.5 19.83 17.5 19C17.5 18.17 18.17 17.5 19 17.5Z" />
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M6 2C4.89 2 4 2.9 4 4V20C4 21.11 4.89 22 6 22H12V20H6V4H13V9H18V12H20V8L14 2M18 14C17.87 14 17.76 14.09 17.74 14.21L17.55 15.53C17.25 15.66 16.96 15.82 16.7 16L15.46 15.5C15.35 15.5 15.22 15.5 15.15 15.63L14.15 17.36C14.09 17.47 14.11 17.6 14.21 17.68L15.27 18.5C15.25 18.67 15.24 18.83 15.24 19C15.24 19.17 15.25 19.33 15.27 19.5L14.21 20.32C14.12 20.4 14.09 20.53 14.15 20.64L15.15 22.37C15.21 22.5 15.34 22.5 15.46 22.5L16.7 22C16.96 22.18 17.24 22.35 17.55 22.47L17.74 23.79C17.76 23.91 17.86 24 18 24H20C20.11 24 20.22 23.91 20.24 23.79L20.43 22.47C20.73 22.34 21 22.18 21.27 22L22.5 22.5C22.63 22.5 22.76 22.5 22.83 22.37L23.83 20.64C23.89 20.53 23.86 20.4 23.77 20.32L22.7 19.5C22.72 19.33 22.74 19.17 22.74 19C22.74 18.83 22.73 18.67 22.7 18.5L23.76 17.68C23.85 17.6 23.88 17.47 23.82 17.36L22.82 15.63C22.76 15.5 22.63 15.5 22.5 15.5L21.27 16C21 15.82 20.73 15.65 20.42 15.53L20.23 14.21C20.22 14.09 20.11 14 20 14M19 17.5C19.83 17.5 20.5 18.17 20.5 19C20.5 19.83 19.83 20.5 19 20.5C18.16 20.5 17.5 19.83 17.5 19C17.5 18.17 18.17 17.5 19 17.5Z" />
|
||||
</vector>
|
|
@ -1,9 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M13 7H11V9H13V7M13 11H11V17H13V11M17 1H7C5.9 1 5 1.9 5 3V21C5 22.1 5.9 23 7 23H17C18.1 23 19 22.1 19 21V3C19 1.9 18.1 1 17 1M17 19H7V5H17V19Z" />
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M13 7H11V9H13V7M13 11H11V17H13V11M17 1H7C5.9 1 5 1.9 5 3V21C5 22.1 5.9 23 7 23H17C18.1 23 19 22.1 19 21V3C19 1.9 18.1 1 17 1M17 19H7V5H17V19Z" />
|
||||
</vector>
|
|
@ -1,9 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M22,3H2C0.91,3.04 0.04,3.91 0,5V19C0.04,20.09 0.91,20.96 2,21H22C23.09,20.96 23.96,20.09 24,19V5C23.96,3.91 23.09,3.04 22,3M22,19H2V5H22V19M14,17V15.75C14,14.09 10.66,13.25 9,13.25C7.34,13.25 4,14.09 4,15.75V17H14M9,7A2.5,2.5 0 0,0 6.5,9.5A2.5,2.5 0 0,0 9,12A2.5,2.5 0 0,0 11.5,9.5A2.5,2.5 0 0,0 9,7M14,7V8H20V7H14M14,9V10H20V9H14M14,11V12H18V11H14" />
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M22,3H2C0.91,3.04 0.04,3.91 0,5V19C0.04,20.09 0.91,20.96 2,21H22C23.09,20.96 23.96,20.09 24,19V5C23.96,3.91 23.09,3.04 22,3M22,19H2V5H22V19M14,17V15.75C14,14.09 10.66,13.25 9,13.25C7.34,13.25 4,14.09 4,15.75V17H14M9,7A2.5,2.5 0 0,0 6.5,9.5A2.5,2.5 0 0,0 9,12A2.5,2.5 0 0,0 11.5,9.5A2.5,2.5 0 0,0 9,7M14,7V8H20V7H14M14,9V10H20V9H14M14,11V12H18V11H14" />
|
||||
</vector>
|
|
@ -1,9 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M2,10.96C1.5,10.68 1.35,10.07 1.63,9.59L3.13,7C3.24,6.8 3.41,6.66 3.6,6.58L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.66,6.72 20.82,6.88 20.91,7.08L22.36,9.6C22.64,10.08 22.47,10.69 22,10.96L21,11.54V16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V10.96C2.7,11.13 2.32,11.14 2,10.96M12,4.15V4.15L12,10.85V10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V12.69L14,15.59C13.67,15.77 13.3,15.76 13,15.6V19.29L19,15.91M13.85,13.36L20.13,9.73L19.55,8.72L13.27,12.35L13.85,13.36Z" />
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M2,10.96C1.5,10.68 1.35,10.07 1.63,9.59L3.13,7C3.24,6.8 3.41,6.66 3.6,6.58L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.66,6.72 20.82,6.88 20.91,7.08L22.36,9.6C22.64,10.08 22.47,10.69 22,10.96L21,11.54V16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V10.96C2.7,11.13 2.32,11.14 2,10.96M12,4.15V4.15L12,10.85V10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V12.69L14,15.59C13.67,15.77 13.3,15.76 13,15.6V19.29L19,15.91M13.85,13.36L20.13,9.73L19.55,8.72L13.27,12.35L13.85,13.36Z" />
|
||||
</vector>
|
|
@ -1,10 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/metadataTextWarning"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z" />
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/metadataTextWarning"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z" />
|
||||
</vector>
|
|
@ -77,5 +77,10 @@
|
|||
<color name="white_alpha_80">#CCFFFFFF</color>
|
||||
<color name="white_alpha_90">#E6FFFFFF</color>
|
||||
|
||||
<!-- Maintenance -->
|
||||
<color name="metadataOk">#77dd77</color>
|
||||
<color name="metadataTextWarning">#FF8C00</color>
|
||||
<color name="metadataTextError">#FF5555</color>
|
||||
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -266,29 +266,62 @@
|
|||
<string name="history_group_glucose">Glucose</string>
|
||||
<string name="mute5min">Mute for 5 minutes</string>
|
||||
|
||||
<!-- <string name="medtronic_pump_status_never_contacted">Never contacted</string>-->
|
||||
<!-- <string name="medtronic_pump_status_waking_up">Waking up</string>-->
|
||||
<!-- <string name="medtronic_pump_status_error_comm">Error with communication</string>-->
|
||||
<!-- <string name="medtronic_pump_status_timeout_comm">Timeout on communication</string>-->
|
||||
<!-- <string name="medtronic_pump_status_pump_unreachable">Pump unreachable</string>-->
|
||||
<!-- <string name="medtronic_pump_status_invalid_config">Invalid configuration</string>-->
|
||||
<!-- <string name="medtronic_pump_status_active">Active</string>-->
|
||||
<!-- <string name="medtronic_pump_status_sleeping">Sleeping</string>-->
|
||||
<!-- Maintenance -->
|
||||
<string name="metadata_label_format">File format</string>
|
||||
<string name="metadata_label_created_at">Created at</string>
|
||||
<string name="metadata_label_aaps_version">AAPS Version</string>
|
||||
<string name="metadata_label_aaps_flavour">Build Variant</string>
|
||||
<string name="metadata_label_device_name">Exporting device patient name</string>
|
||||
<string name="metadata_label_device_model">Exporting device model</string>
|
||||
<string name="metadata_label_encryption">File encryption</string>
|
||||
<string name="metadata_format_old">Old export format</string>
|
||||
<string name="metadata_format_new">New encrypted format</string>
|
||||
<string name="metadata_format_debug">New debug format (unencrypted)</string>
|
||||
<string name="metadata_format_other">Unknown export format</string>
|
||||
<string name="exported_ago" comment="at placeholder we add pluralized number of hours/minutes">exported %1$s ago</string>
|
||||
<string name="exported_at" comment="at placeholder we add export date">exported at %1$s</string>
|
||||
<string name="exported_less_than_hour_ago">exported less than hour ago</string>
|
||||
<string name="in_directory" comment="placeholder is for exported file path">in directory: %1$s</string>
|
||||
<string name="preferences_import_list_title">Select file to import</string>
|
||||
<string name="metadata_warning_different_flavour">Preferences were created with different variant of AAPS (%1$s) while you have: %2$s.\n\nSome settings may be missing or invalid - after importing please check and update your preferences.</string>
|
||||
<string name="metadata_warning_different_device">Preferences were created on a different device. It is OK if you are importing from older/different phone, but make sure imported preferences are correct!</string>
|
||||
<string name="metadata_warning_outdated_format">You are using the outdated legacy format from old versions of AAPS, which is not secure! Only use it as a last resort, if you do not have an export in current, JSON format.</string>
|
||||
<string name="metadata_warning_old_export">Imported preferences are already %1$s days old! Maybe you have more up-to-date preferences or you choose the wrong file? Remember to export preferences regularly!</string>
|
||||
<string name="metadata_warning_date_format">Invalid date-time format!</string>
|
||||
<string name="metadata_warning_different_version">Preferences from different minor version of application. It is OK if you are importing after upgrade, but check after import if preferences are still correct!</string>
|
||||
<string name="metadata_urgent_different_version">Preferences from different major version of application. Major versions differ significantly and may have incompatible preferences! Make sure after import that preferences are still correct!</string>
|
||||
<string name="prefdecrypt_settings_tampered">Settings file tampered</string>
|
||||
<string name="prefdecrypt_settings_secure">Settings file is secure</string>
|
||||
<string name="prefdecrypt_settings_unencrypted">Using not secure, unencrypted settings format</string>
|
||||
<string name="prefdecrypt_wrong_json">JSON format error, missing required field (format, content, metadata or security)</string>
|
||||
<string name="prefdecrypt_wrong_password">Decryption error, the given password cannot decrypt the file</string>
|
||||
<string name="prefdecrypt_issue_missing_file_hash">File checksum (hash) missing, cannot verify the authenticity of settings!</string>
|
||||
<string name="prefdecrypt_issue_modified">File was modified after export!</string>
|
||||
<string name="prefdecrypt_issue_parsing">Decryption error, parsing preferences failed!</string>
|
||||
<string name="prefdecrypt_issue_wrong_pass">Decryption error, the provided password is invalid or settings file was modified! It may happen that the imported file was exported with a different Master password.</string>
|
||||
<string name="prefdecrypt_issue_wrong_format">Missing encryption configuration, settings format is invalid!</string>
|
||||
<string name="prefdecrypt_issue_wrong_algorithm">Unsupported or not specified encryption algorithm!</string>
|
||||
|
||||
<!-- <string name="danar_history_alarm">Alarms</string>-->
|
||||
<!-- <string name="danar_history_glucose">Glucose</string>-->
|
||||
|
||||
|
||||
<!-- All(R.string.medtronic_history_group_all), //-->
|
||||
<!-- Bolus(R.string.danar_history_bolus), //-->
|
||||
<!-- Basal(R.string.medtronic_history_group_basal), //-->
|
||||
<!-- Prime(R.string.danar_history_prime), //-->
|
||||
<!-- Configuration(R.string.medtronic_history_group_configuration), //-->
|
||||
<!-- Alarm(R.string.danar_history_alarm), //-->
|
||||
<!-- Glucose(R.string.danar_history_glucose), //-->
|
||||
<!-- Notification(R.string.medtronic_history_group_notification), //-->
|
||||
<!-- Statistic(R.string.medtronic_history_group_statistic),-->
|
||||
<!-- Unknown(R.string.medtronic_history_group_unknown), //-->
|
||||
<!-- VersionChecker -->
|
||||
<string name="key_last_time_this_version_detected" translatable="false">last_time_this_version_detected</string>
|
||||
<string name="key_last_versionchecker_warning" translatable="false">last_versionchecker_waring</string>
|
||||
<string name="key_last_versionchecker_plugin_warning" translatable="false">last_versionchecker_plugin_waring</string>
|
||||
<string name="key_last_revoked_certs_check" translatable="false">last_revoked_certs_check</string>
|
||||
<string name="signature_verifier">Signature verifier</string>
|
||||
<string name="running_invalid_version">We have detected that you are running an invalid version. Loop disabled!</string>
|
||||
<string name="versionavailable">Version %1$s available</string>
|
||||
|
||||
<plurals name="days">
|
||||
<item quantity="one">%1$d day</item>
|
||||
<item quantity="other">%1$d days</item>
|
||||
</plurals>
|
||||
<plurals name="hours">
|
||||
<item quantity="one">%1$d hour</item>
|
||||
<item quantity="other">%1$d hours</item>
|
||||
</plurals>
|
||||
<plurals name="minutes">
|
||||
<item quantity="one">%1$d minute</item>
|
||||
<item quantity="other">%1$d minutes</item>
|
||||
</plurals>
|
||||
|
||||
</resources>
|
Loading…
Reference in a new issue