Merge remote-tracking branch 'origin/dagger3' into rs
This commit is contained in:
commit
013018ac7b
103 changed files with 3990 additions and 1674 deletions
|
@ -28,6 +28,8 @@ ext {
|
|||
ormLiteVersion = "4.46"
|
||||
powermockVersion = "1.7.3"
|
||||
dexmakerVersion = "1.2"
|
||||
retrofit2Version = '2.8.1'
|
||||
okhttp3Version="4.4.1"
|
||||
}
|
||||
|
||||
|
||||
|
@ -224,9 +226,9 @@ dependencies {
|
|||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation 'com.google.android.gms:play-services-wearable:17.0.0'
|
||||
implementation "com.google.android.gms:play-services-location:17.0.0"
|
||||
implementation 'com.google.firebase:firebase-core:17.2.1'
|
||||
implementation 'com.google.firebase:firebase-auth:19.2.0'
|
||||
implementation 'com.google.firebase:firebase-database:19.2.0'
|
||||
implementation 'com.google.firebase:firebase-core:17.2.3'
|
||||
implementation 'com.google.firebase:firebase-auth:19.3.0'
|
||||
implementation 'com.google.firebase:firebase-database:19.2.1'
|
||||
implementation('com.crashlytics.sdk.android:crashlytics:2.10.1@aar') {
|
||||
transitive = true;
|
||||
}
|
||||
|
@ -240,7 +242,7 @@ dependencies {
|
|||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
implementation 'androidx.percentlayout:percentlayout:1.0.0'
|
||||
implementation "androidx.preference:preference-ktx:1.1.0"
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
|
||||
|
||||
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
|
||||
|
@ -251,7 +253,7 @@ dependencies {
|
|||
exclude group: "com.google.android", module: "android"
|
||||
}
|
||||
implementation "org.apache.commons:commons-lang3:3.9"
|
||||
implementation "org.slf4j:slf4j-api:1.7.29"
|
||||
implementation 'org.slf4j:slf4j-api:1.7.30'
|
||||
// Graphview cannot be upgraded
|
||||
implementation "com.jjoe64:graphview:4.0.1"
|
||||
implementation "com.joanzapata.iconify:android-iconify-fontawesome:2.2.2"
|
||||
|
@ -288,7 +290,7 @@ dependencies {
|
|||
testImplementation "org.powermock:powermock-module-junit4-rule:${powermockVersion}"
|
||||
testImplementation "org.powermock:powermock-module-junit4:${powermockVersion}"
|
||||
testImplementation "joda-time:joda-time:2.10.5"
|
||||
testImplementation("com.google.truth:truth:0.39") {
|
||||
testImplementation('com.google.truth:truth:1.0.1') {
|
||||
exclude group: "com.google.guava", module: "guava"
|
||||
exclude group: "com.google.code.findbugs", module: "jsr305"
|
||||
}
|
||||
|
@ -300,11 +302,11 @@ dependencies {
|
|||
|
||||
|
||||
// new for tidepool
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.2.2'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
|
||||
implementation "com.squareup.retrofit2:retrofit:2.6.2"
|
||||
implementation "com.squareup.retrofit2:adapter-rxjava2:2.6.2"
|
||||
implementation "com.squareup.retrofit2:converter-gson:2.6.2"
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttp3Version"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp3Version"
|
||||
implementation "com.squareup.retrofit2:retrofit:$retrofit2Version"
|
||||
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit2Version"
|
||||
implementation "com.squareup.retrofit2:converter-gson:$retrofit2Version"
|
||||
|
||||
// Phone checker
|
||||
implementation 'com.scottyab:rootbeer-lib:0.0.7'
|
||||
|
|
|
@ -1,370 +0,0 @@
|
|||
package info.nightscout.androidaps;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.os.PersistableBundle;
|
||||
import android.text.SpannableString;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.util.Linkify;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.joanzapata.iconify.Iconify;
|
||||
import com.joanzapata.iconify.fonts.FontAwesomeModule;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.android.AndroidInjection;
|
||||
import info.nightscout.androidaps.activities.NoSplashAppCompatActivity;
|
||||
import info.nightscout.androidaps.activities.PreferencesActivity;
|
||||
import info.nightscout.androidaps.activities.SingleFragmentActivity;
|
||||
import info.nightscout.androidaps.activities.StatsActivity;
|
||||
import info.nightscout.androidaps.events.EventAppExit;
|
||||
import info.nightscout.androidaps.events.EventPreferenceChange;
|
||||
import info.nightscout.androidaps.events.EventRebuildTabs;
|
||||
import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity;
|
||||
import info.nightscout.androidaps.interfaces.ActivePluginProvider;
|
||||
import info.nightscout.androidaps.interfaces.PluginBase;
|
||||
import info.nightscout.androidaps.interfaces.PluginType;
|
||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
||||
import info.nightscout.androidaps.logging.LTag;
|
||||
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
|
||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
|
||||
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils;
|
||||
import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus;
|
||||
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin;
|
||||
import info.nightscout.androidaps.setupwizard.SetupWizardActivity;
|
||||
import info.nightscout.androidaps.tabs.TabPageAdapter;
|
||||
import info.nightscout.androidaps.utils.AndroidPermission;
|
||||
import info.nightscout.androidaps.utils.FabricPrivacy;
|
||||
import info.nightscout.androidaps.utils.LocaleHelper;
|
||||
import info.nightscout.androidaps.utils.OKDialog;
|
||||
import info.nightscout.androidaps.utils.buildHelper.BuildHelper;
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP;
|
||||
import info.nightscout.androidaps.utils.protection.ProtectionCheck;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
import static info.nightscout.androidaps.utils.extensions.EspressoTestHelperKt.isRunningRealPumpTest;
|
||||
|
||||
public class MainActivity extends NoSplashAppCompatActivity {
|
||||
|
||||
private CompositeDisposable disposable = new CompositeDisposable();
|
||||
private ActionBarDrawerToggle actionBarDrawerToggle;
|
||||
private MenuItem pluginPreferencesMenuItem;
|
||||
|
||||
@Inject AAPSLogger aapsLogger;
|
||||
@Inject RxBusWrapper rxBus;
|
||||
@Inject AndroidPermission androidPermission;
|
||||
@Inject SP sp;
|
||||
@Inject ResourceHelper resourceHelper;
|
||||
@Inject VersionCheckerUtils versionCheckerUtils;
|
||||
@Inject SmsCommunicatorPlugin smsCommunicatorPlugin;
|
||||
@Inject LoopPlugin loopPlugin;
|
||||
@Inject NSSettingsStatus nsSettingsStatus;
|
||||
@Inject BuildHelper buildHelper;
|
||||
@Inject ActivePluginProvider activePlugin;
|
||||
@Inject FabricPrivacy fabricPrivacy;
|
||||
@Inject ProtectionCheck protectionCheck;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Iconify.with(new FontAwesomeModule());
|
||||
LocaleHelper.INSTANCE.update(getApplicationContext());
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
setSupportActionBar(findViewById(R.id.toolbar));
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
|
||||
DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
|
||||
actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.open_navigation, R.string.close_navigation);
|
||||
drawerLayout.addDrawerListener(actionBarDrawerToggle);
|
||||
actionBarDrawerToggle.syncState();
|
||||
|
||||
// initialize screen wake lock
|
||||
processPreferenceChange(new EventPreferenceChange(resourceHelper.gs(R.string.key_keep_screen_on)));
|
||||
|
||||
final ViewPager viewPager = findViewById(R.id.pager);
|
||||
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
checkPluginPreferences(viewPager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
}
|
||||
});
|
||||
|
||||
//Check here if loop plugin is disabled. Else check via constraints
|
||||
if (!loopPlugin.isEnabled(PluginType.LOOP))
|
||||
versionCheckerUtils.triggerCheckVersion();
|
||||
|
||||
fabricPrivacy.setUserStats();
|
||||
|
||||
setupTabs();
|
||||
setupViews();
|
||||
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventRebuildTabs.class)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(event -> {
|
||||
LocaleHelper.INSTANCE.update(getApplicationContext());
|
||||
if (event.getRecreate()) {
|
||||
recreate();
|
||||
} else {
|
||||
setupTabs();
|
||||
setupViews();
|
||||
}
|
||||
setWakeLock();
|
||||
}, exception -> fabricPrivacy.logException(exception))
|
||||
);
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventPreferenceChange.class)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::processPreferenceChange, exception -> fabricPrivacy.logException(exception))
|
||||
);
|
||||
|
||||
if (!sp.getBoolean(R.string.key_setupwizard_processed, false) && !isRunningRealPumpTest()) {
|
||||
Intent intent = new Intent(this, SetupWizardActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
androidPermission.notifyForStoragePermission(this);
|
||||
androidPermission.notifyForBatteryOptimizationPermission(this);
|
||||
if (Config.PUMPDRIVERS) {
|
||||
androidPermission.notifyForLocationPermissions(this);
|
||||
androidPermission.notifyForSMSPermissions(this, smsCommunicatorPlugin);
|
||||
androidPermission.notifyForSystemWindowPermissions(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPluginPreferences(ViewPager viewPager) {
|
||||
if (pluginPreferencesMenuItem == null) return;
|
||||
if (((TabPageAdapter) viewPager.getAdapter()).getPluginAt(viewPager.getCurrentItem()).getPreferencesId() != -1)
|
||||
pluginPreferencesMenuItem.setEnabled(true);
|
||||
else pluginPreferencesMenuItem.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
|
||||
super.onPostCreate(savedInstanceState, persistentState);
|
||||
actionBarDrawerToggle.syncState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
disposable.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
protectionCheck.queryProtection(this, ProtectionCheck.Protection.APPLICATION, null, this::finish, this::finish);
|
||||
}
|
||||
|
||||
private void setWakeLock() {
|
||||
boolean keepScreenOn = sp.getBoolean(R.string.key_keep_screen_on, false);
|
||||
if (keepScreenOn)
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
else
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
|
||||
public void processPreferenceChange(final EventPreferenceChange ev) {
|
||||
if (ev.isChanged(resourceHelper, R.string.key_keep_screen_on))
|
||||
setWakeLock();
|
||||
}
|
||||
|
||||
private void setupViews() {
|
||||
TabPageAdapter pageAdapter = new TabPageAdapter(getSupportFragmentManager(), this);
|
||||
NavigationView navigationView = findViewById(R.id.navigation_view);
|
||||
navigationView.setNavigationItemSelectedListener(menuItem -> true);
|
||||
Menu menu = navigationView.getMenu();
|
||||
menu.clear();
|
||||
for (PluginBase p : activePlugin.getPluginsList()) {
|
||||
pageAdapter.registerNewFragment(p);
|
||||
if (p.hasFragment() && !p.isFragmentVisible() && p.isEnabled(p.getPluginDescription().getType()) && !p.getPluginDescription().neverVisible) {
|
||||
MenuItem menuItem = menu.add(p.getName());
|
||||
menuItem.setCheckable(true);
|
||||
menuItem.setOnMenuItemClickListener(item -> {
|
||||
Intent intent = new Intent(this, SingleFragmentActivity.class);
|
||||
intent.putExtra("plugin", activePlugin.getPluginsList().indexOf(p));
|
||||
startActivity(intent);
|
||||
((DrawerLayout) findViewById(R.id.drawer_layout)).closeDrawers();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
ViewPager mPager = findViewById(R.id.pager);
|
||||
mPager.setAdapter(pageAdapter);
|
||||
//if (switchToLast)
|
||||
// mPager.setCurrentItem(pageAdapter.getCount() - 1, false);
|
||||
checkPluginPreferences(mPager);
|
||||
}
|
||||
|
||||
private void setupTabs() {
|
||||
ViewPager viewPager = findViewById(R.id.pager);
|
||||
TabLayout normalTabs = findViewById(R.id.tabs_normal);
|
||||
normalTabs.setupWithViewPager(viewPager, true);
|
||||
TabLayout compactTabs = findViewById(R.id.tabs_compact);
|
||||
compactTabs.setupWithViewPager(viewPager, true);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
if (sp.getBoolean("short_tabtitles", false)) {
|
||||
normalTabs.setVisibility(View.GONE);
|
||||
compactTabs.setVisibility(View.VISIBLE);
|
||||
toolbar.setLayoutParams(new LinearLayout.LayoutParams(Toolbar.LayoutParams.MATCH_PARENT, (int) getResources().getDimension(R.dimen.compact_height)));
|
||||
} else {
|
||||
normalTabs.setVisibility(View.VISIBLE);
|
||||
compactTabs.setVisibility(View.GONE);
|
||||
TypedValue typedValue = new TypedValue();
|
||||
if (getTheme().resolveAttribute(R.attr.actionBarSize, typedValue, true)) {
|
||||
toolbar.setLayoutParams(new LinearLayout.LayoutParams(Toolbar.LayoutParams.MATCH_PARENT,
|
||||
TypedValue.complexToDimensionPixelSize(typedValue.data, getResources().getDisplayMetrics())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (permissions.length != 0) {
|
||||
if (ActivityCompat.checkSelfPermission(this, permissions[0]) == PackageManager.PERMISSION_GRANTED) {
|
||||
switch (requestCode) {
|
||||
case AndroidPermission.CASE_STORAGE:
|
||||
//show dialog after permission is granted
|
||||
OKDialog.show(this, "", resourceHelper.gs(R.string.alert_dialog_storage_permission_text));
|
||||
break;
|
||||
case AndroidPermission.CASE_LOCATION:
|
||||
case AndroidPermission.CASE_SMS:
|
||||
case AndroidPermission.CASE_BATTERY:
|
||||
case AndroidPermission.CASE_PHONE_STATE:
|
||||
case AndroidPermission.CASE_SYSTEM_WINDOW:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
View v = getCurrentFocus();
|
||||
if (v instanceof EditText) {
|
||||
Rect outRect = new Rect();
|
||||
v.getGlobalVisibleRect(outRect);
|
||||
if (!outRect.contains((int) event.getRawX(), (int) event.getRawY())) {
|
||||
v.clearFocus();
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.dispatchTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||
pluginPreferencesMenuItem = menu.findItem(R.id.nav_plugin_preferences);
|
||||
checkPluginPreferences(findViewById(R.id.pager));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
switch (id) {
|
||||
case R.id.nav_preferences:
|
||||
protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, () -> {
|
||||
Intent i = new Intent(this, PreferencesActivity.class);
|
||||
i.putExtra("id", -1);
|
||||
startActivity(i);
|
||||
});
|
||||
return true;
|
||||
case R.id.nav_historybrowser:
|
||||
startActivity(new Intent(this, HistoryBrowseActivity.class));
|
||||
return true;
|
||||
case R.id.nav_setupwizard:
|
||||
startActivity(new Intent(this, SetupWizardActivity.class));
|
||||
return true;
|
||||
case R.id.nav_about:
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(resourceHelper.gs(R.string.app_name) + " " + BuildConfig.VERSION);
|
||||
builder.setIcon(resourceHelper.getIcon());
|
||||
String message = "Build: " + BuildConfig.BUILDVERSION + "\n";
|
||||
message += "Flavor: " + BuildConfig.FLAVOR + BuildConfig.BUILD_TYPE + "\n";
|
||||
message += resourceHelper.gs(R.string.configbuilder_nightscoutversion_label) + " " + nsSettingsStatus.getNightscoutVersionName();
|
||||
if (buildHelper.isEngineeringMode())
|
||||
message += "\n" + resourceHelper.gs(R.string.engineering_mode_enabled);
|
||||
message += resourceHelper.gs(R.string.about_link_urls);
|
||||
final SpannableString messageSpanned = new SpannableString(message);
|
||||
Linkify.addLinks(messageSpanned, Linkify.WEB_URLS);
|
||||
builder.setMessage(messageSpanned);
|
||||
builder.setPositiveButton(resourceHelper.gs(R.string.ok), null);
|
||||
AlertDialog alertDialog = builder.create();
|
||||
alertDialog.show();
|
||||
((TextView) alertDialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance());
|
||||
return true;
|
||||
case R.id.nav_exit:
|
||||
aapsLogger.debug(LTag.CORE, "Exiting");
|
||||
rxBus.send(new EventAppExit());
|
||||
finish();
|
||||
System.runFinalization();
|
||||
System.exit(0);
|
||||
return true;
|
||||
case R.id.nav_plugin_preferences:
|
||||
ViewPager viewPager = findViewById(R.id.pager);
|
||||
final PluginBase plugin = ((TabPageAdapter) viewPager.getAdapter()).getPluginAt(viewPager.getCurrentItem());
|
||||
protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, () -> {
|
||||
Intent i = new Intent(this, PreferencesActivity.class);
|
||||
i.putExtra("id", plugin.getPreferencesId());
|
||||
startActivity(i);
|
||||
});
|
||||
return true;
|
||||
/*
|
||||
case R.id.nav_survey:
|
||||
startActivity(new Intent(this, SurveyActivity.class));
|
||||
return true;
|
||||
*/
|
||||
case R.id.nav_stats:
|
||||
startActivity(new Intent(this, StatsActivity.class));
|
||||
return true;
|
||||
}
|
||||
return actionBarDrawerToggle.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
}
|
333
app/src/main/java/info/nightscout/androidaps/MainActivity.kt
Normal file
333
app/src/main/java/info/nightscout/androidaps/MainActivity.kt
Normal file
|
@ -0,0 +1,333 @@
|
|||
package info.nightscout.androidaps
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.os.PersistableBundle
|
||||
import android.text.SpannableString
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.text.util.Linkify
|
||||
import android.util.TypedValue
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.joanzapata.iconify.Iconify
|
||||
import com.joanzapata.iconify.fonts.FontAwesomeModule
|
||||
import info.nightscout.androidaps.activities.NoSplashAppCompatActivity
|
||||
import info.nightscout.androidaps.activities.PreferencesActivity
|
||||
import info.nightscout.androidaps.activities.SingleFragmentActivity
|
||||
import info.nightscout.androidaps.activities.StatsActivity
|
||||
import info.nightscout.androidaps.events.EventAppExit
|
||||
import info.nightscout.androidaps.events.EventPreferenceChange
|
||||
import info.nightscout.androidaps.events.EventRebuildTabs
|
||||
import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity
|
||||
import info.nightscout.androidaps.interfaces.ActivePluginProvider
|
||||
import info.nightscout.androidaps.interfaces.PluginType
|
||||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
import info.nightscout.androidaps.logging.LTag
|
||||
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
|
||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
||||
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils
|
||||
import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus
|
||||
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
|
||||
import info.nightscout.androidaps.setupwizard.SetupWizardActivity
|
||||
import info.nightscout.androidaps.tabs.TabPageAdapter
|
||||
import info.nightscout.androidaps.utils.AndroidPermission
|
||||
import info.nightscout.androidaps.utils.FabricPrivacy
|
||||
import info.nightscout.androidaps.utils.LocaleHelper.update
|
||||
import info.nightscout.androidaps.utils.OKDialog.show
|
||||
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
|
||||
import info.nightscout.androidaps.utils.extensions.isRunningRealPumpTest
|
||||
import info.nightscout.androidaps.utils.protection.ProtectionCheck
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class MainActivity : NoSplashAppCompatActivity() {
|
||||
private val disposable = CompositeDisposable()
|
||||
|
||||
@Inject lateinit var aapsLogger: AAPSLogger
|
||||
@Inject lateinit var rxBus: RxBusWrapper
|
||||
@Inject lateinit var androidPermission: AndroidPermission
|
||||
@Inject lateinit var sp: SP
|
||||
@Inject lateinit var resourceHelper: ResourceHelper
|
||||
@Inject lateinit var versionCheckerUtils: VersionCheckerUtils
|
||||
@Inject lateinit var smsCommunicatorPlugin: SmsCommunicatorPlugin
|
||||
@Inject lateinit var loopPlugin: LoopPlugin
|
||||
@Inject lateinit var nsSettingsStatus: NSSettingsStatus
|
||||
@Inject lateinit var buildHelper: BuildHelper
|
||||
@Inject lateinit var activePlugin: ActivePluginProvider
|
||||
@Inject lateinit var fabricPrivacy: FabricPrivacy
|
||||
@Inject lateinit var protectionCheck: ProtectionCheck
|
||||
|
||||
private lateinit var actionBarDrawerToggle: ActionBarDrawerToggle
|
||||
private var pluginPreferencesMenuItem: MenuItem? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Iconify.with(FontAwesomeModule())
|
||||
update(applicationContext)
|
||||
setContentView(R.layout.activity_main)
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setHomeButtonEnabled(true)
|
||||
actionBarDrawerToggle = ActionBarDrawerToggle(this, drawer_layout, R.string.open_navigation, R.string.close_navigation).also {
|
||||
drawer_layout.addDrawerListener(it)
|
||||
it.syncState()
|
||||
}
|
||||
|
||||
// initialize screen wake lock
|
||||
processPreferenceChange(EventPreferenceChange(resourceHelper.gs(R.string.key_keep_screen_on)))
|
||||
pager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
|
||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
|
||||
override fun onPageSelected(position: Int) {
|
||||
checkPluginPreferences(pager)
|
||||
}
|
||||
|
||||
override fun onPageScrollStateChanged(state: Int) {}
|
||||
})
|
||||
|
||||
//Check here if loop plugin is disabled. Else check via constraints
|
||||
if (!loopPlugin.isEnabled(PluginType.LOOP)) versionCheckerUtils.triggerCheckVersion()
|
||||
fabricPrivacy.setUserStats()
|
||||
setupTabs()
|
||||
setupViews()
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventRebuildTabs::class.java)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({
|
||||
update(applicationContext)
|
||||
if (it.recreate) recreate()
|
||||
else {
|
||||
setupTabs()
|
||||
setupViews()
|
||||
}
|
||||
setWakeLock()
|
||||
}) { fabricPrivacy.logException(it) }
|
||||
)
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventPreferenceChange::class.java)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ processPreferenceChange(it) }) { fabricPrivacy.logException(it) }
|
||||
)
|
||||
if (!sp.getBoolean(R.string.key_setupwizard_processed, false) && !isRunningRealPumpTest()) {
|
||||
val intent = Intent(this, SetupWizardActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
androidPermission.notifyForStoragePermission(this)
|
||||
androidPermission.notifyForBatteryOptimizationPermission(this)
|
||||
if (Config.PUMPDRIVERS) {
|
||||
androidPermission.notifyForLocationPermissions(this)
|
||||
androidPermission.notifyForSMSPermissions(this, smsCommunicatorPlugin)
|
||||
androidPermission.notifyForSystemWindowPermissions(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPluginPreferences(viewPager: ViewPager) {
|
||||
pluginPreferencesMenuItem?.isEnabled = (viewPager.adapter as TabPageAdapter).getPluginAt(viewPager.currentItem).preferencesId != -1
|
||||
}
|
||||
|
||||
override fun onPostCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
|
||||
super.onPostCreate(savedInstanceState, persistentState)
|
||||
actionBarDrawerToggle.syncState()
|
||||
}
|
||||
|
||||
public override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
disposable.clear()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
protectionCheck.queryProtection(this, ProtectionCheck.Protection.APPLICATION, null, Runnable { finish() }, Runnable { finish() })
|
||||
}
|
||||
|
||||
private fun setWakeLock() {
|
||||
val keepScreenOn = sp.getBoolean(R.string.key_keep_screen_on, false)
|
||||
if (keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) else window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
|
||||
private fun processPreferenceChange(ev: EventPreferenceChange) {
|
||||
if (ev.isChanged(resourceHelper, R.string.key_keep_screen_on)) setWakeLock()
|
||||
}
|
||||
|
||||
private fun setupViews() {
|
||||
val pageAdapter = TabPageAdapter(supportFragmentManager, this)
|
||||
val navigationView = findViewById<NavigationView>(R.id.navigation_view)
|
||||
navigationView.setNavigationItemSelectedListener { true }
|
||||
val menu = navigationView.menu.also { it.clear() }
|
||||
for (p in activePlugin.pluginsList) {
|
||||
pageAdapter.registerNewFragment(p)
|
||||
if (p.hasFragment() && !p.isFragmentVisible() && p.isEnabled(p.pluginDescription.type) && !p.pluginDescription.neverVisible) {
|
||||
val menuItem = menu.add(p.name)
|
||||
menuItem.isCheckable = true
|
||||
menuItem.setOnMenuItemClickListener {
|
||||
val intent = Intent(this, SingleFragmentActivity::class.java)
|
||||
intent.putExtra("plugin", activePlugin.pluginsList.indexOf(p))
|
||||
startActivity(intent)
|
||||
(findViewById<View>(R.id.drawer_layout) as DrawerLayout).closeDrawers()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
val mPager = findViewById<ViewPager>(R.id.pager)
|
||||
mPager.adapter = pageAdapter
|
||||
//if (switchToLast)
|
||||
// mPager.setCurrentItem(pageAdapter.getCount() - 1, false);
|
||||
checkPluginPreferences(mPager)
|
||||
}
|
||||
|
||||
private fun setupTabs() {
|
||||
val viewPager = findViewById<ViewPager>(R.id.pager)
|
||||
val normalTabs = findViewById<TabLayout>(R.id.tabs_normal)
|
||||
normalTabs.setupWithViewPager(viewPager, true)
|
||||
val compactTabs = findViewById<TabLayout>(R.id.tabs_compact)
|
||||
compactTabs.setupWithViewPager(viewPager, true)
|
||||
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
||||
if (sp.getBoolean(R.string.key_short_tabtitles, false)) {
|
||||
normalTabs.visibility = View.GONE
|
||||
compactTabs.visibility = View.VISIBLE
|
||||
toolbar.layoutParams = LinearLayout.LayoutParams(Toolbar.LayoutParams.MATCH_PARENT, resources.getDimension(R.dimen.compact_height).toInt())
|
||||
} else {
|
||||
normalTabs.visibility = View.VISIBLE
|
||||
compactTabs.visibility = View.GONE
|
||||
val typedValue = TypedValue()
|
||||
if (theme.resolveAttribute(R.attr.actionBarSize, typedValue, true)) {
|
||||
toolbar.layoutParams = LinearLayout.LayoutParams(Toolbar.LayoutParams.MATCH_PARENT,
|
||||
TypedValue.complexToDimensionPixelSize(typedValue.data, resources.displayMetrics))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (permissions.isNotEmpty()) {
|
||||
if (ActivityCompat.checkSelfPermission(this, permissions[0]) == PackageManager.PERMISSION_GRANTED) {
|
||||
when (requestCode) {
|
||||
AndroidPermission.CASE_STORAGE -> //show dialog after permission is granted
|
||||
show(this, "", resourceHelper.gs(R.string.alert_dialog_storage_permission_text))
|
||||
|
||||
AndroidPermission.CASE_LOCATION, AndroidPermission.CASE_SMS, AndroidPermission.CASE_BATTERY, AndroidPermission.CASE_PHONE_STATE, AndroidPermission.CASE_SYSTEM_WINDOW -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
||||
if (event.action == MotionEvent.ACTION_DOWN) {
|
||||
val v = currentFocus
|
||||
if (v is EditText) {
|
||||
val outRect = Rect()
|
||||
v.getGlobalVisibleRect(outRect)
|
||||
if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
|
||||
v.clearFocus()
|
||||
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(v.getWindowToken(), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.dispatchTouchEvent(event)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_main, menu)
|
||||
pluginPreferencesMenuItem = menu.findItem(R.id.nav_plugin_preferences)
|
||||
checkPluginPreferences(findViewById(R.id.pager))
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.nav_preferences -> {
|
||||
protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, Runnable {
|
||||
val i = Intent(this, PreferencesActivity::class.java)
|
||||
i.putExtra("id", -1)
|
||||
startActivity(i)
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.nav_historybrowser -> {
|
||||
startActivity(Intent(this, HistoryBrowseActivity::class.java))
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.nav_setupwizard -> {
|
||||
startActivity(Intent(this, SetupWizardActivity::class.java))
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.nav_about -> {
|
||||
var message = "Build: ${BuildConfig.BUILDVERSION}\n"
|
||||
message += "Flavor: ${BuildConfig.FLAVOR}${BuildConfig.BUILD_TYPE}\n"
|
||||
message += "${resourceHelper.gs(R.string.configbuilder_nightscoutversion_label)} ${nsSettingsStatus.nightscoutVersionName}"
|
||||
if (buildHelper.isEngineeringMode()) message += "\n${resourceHelper.gs(R.string.engineering_mode_enabled)}"
|
||||
message += resourceHelper.gs(R.string.about_link_urls)
|
||||
val messageSpanned = SpannableString(message)
|
||||
Linkify.addLinks(messageSpanned, Linkify.WEB_URLS)
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(resourceHelper.gs(R.string.app_name) + " " + BuildConfig.VERSION)
|
||||
.setIcon(resourceHelper.getIcon())
|
||||
.setMessage(messageSpanned)
|
||||
.setPositiveButton(resourceHelper.gs(R.string.ok), null)
|
||||
.create().also {
|
||||
it.show()
|
||||
(it.findViewById<View>(android.R.id.message) as TextView).movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.nav_exit -> {
|
||||
aapsLogger.debug(LTag.CORE, "Exiting")
|
||||
rxBus.send(EventAppExit())
|
||||
finish()
|
||||
System.runFinalization()
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
R.id.nav_plugin_preferences -> {
|
||||
val viewPager = findViewById<ViewPager>(R.id.pager)
|
||||
val plugin = (viewPager.adapter as TabPageAdapter).getPluginAt(viewPager.currentItem)
|
||||
protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, Runnable {
|
||||
val i = Intent(this, PreferencesActivity::class.java)
|
||||
i.putExtra("id", plugin.preferencesId)
|
||||
startActivity(i)
|
||||
})
|
||||
return true
|
||||
}
|
||||
/*
|
||||
R.id.nav_survey -> {
|
||||
startActivity(Intent(this, SurveyActivity::class.java))
|
||||
return true
|
||||
}
|
||||
*/
|
||||
R.id.nav_stats -> {
|
||||
startActivity(Intent(this, StatsActivity::class.java))
|
||||
return true
|
||||
}
|
||||
}
|
||||
return actionBarDrawerToggle.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package info.nightscout.androidaps;
|
|||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
|
@ -25,6 +26,8 @@ import net.danlew.android.joda.JodaTimeAndroid;
|
|||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.android.AndroidInjector;
|
||||
|
@ -33,65 +36,15 @@ import dagger.android.HasAndroidInjector;
|
|||
import info.nightscout.androidaps.data.Profile;
|
||||
import info.nightscout.androidaps.db.DatabaseHelper;
|
||||
import info.nightscout.androidaps.dependencyInjection.DaggerAppComponent;
|
||||
import info.nightscout.androidaps.interfaces.PluginBase;
|
||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
||||
import info.nightscout.androidaps.logging.LTag;
|
||||
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
|
||||
import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin;
|
||||
import info.nightscout.androidaps.plugins.aps.openAPSMA.OpenAPSMAPlugin;
|
||||
import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin;
|
||||
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
|
||||
import info.nightscout.androidaps.plugins.configBuilder.PluginStore;
|
||||
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction;
|
||||
import info.nightscout.androidaps.plugins.constraints.dstHelper.DstHelperPlugin;
|
||||
import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin;
|
||||
import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin;
|
||||
import info.nightscout.androidaps.plugins.constraints.signatureVerifier.SignatureVerifierPlugin;
|
||||
import info.nightscout.androidaps.plugins.constraints.storage.StorageConstraintPlugin;
|
||||
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerPlugin;
|
||||
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils;
|
||||
import info.nightscout.androidaps.plugins.general.actions.ActionsPlugin;
|
||||
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin;
|
||||
import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin;
|
||||
import info.nightscout.androidaps.plugins.general.dataBroadcaster.DataBroadcastPlugin;
|
||||
import info.nightscout.androidaps.plugins.general.food.FoodPlugin;
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin;
|
||||
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin;
|
||||
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload;
|
||||
import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin;
|
||||
import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin;
|
||||
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin;
|
||||
import info.nightscout.androidaps.plugins.general.tidepool.TidepoolPlugin;
|
||||
import info.nightscout.androidaps.plugins.general.wear.WearPlugin;
|
||||
import info.nightscout.androidaps.plugins.general.xdripStatusline.StatusLinePlugin;
|
||||
import info.nightscout.androidaps.plugins.insulin.InsulinOrefFreePeakPlugin;
|
||||
import info.nightscout.androidaps.plugins.insulin.InsulinOrefRapidActingPlugin;
|
||||
import info.nightscout.androidaps.plugins.insulin.InsulinOrefUltraRapidActingPlugin;
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin;
|
||||
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin;
|
||||
import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin;
|
||||
import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin;
|
||||
import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin;
|
||||
import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin;
|
||||
import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin;
|
||||
import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin;
|
||||
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin;
|
||||
import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
|
||||
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin;
|
||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin;
|
||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin;
|
||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin;
|
||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin;
|
||||
import info.nightscout.androidaps.plugins.source.DexcomPlugin;
|
||||
import info.nightscout.androidaps.plugins.source.EversensePlugin;
|
||||
import info.nightscout.androidaps.plugins.source.GlimpPlugin;
|
||||
import info.nightscout.androidaps.plugins.source.MM640gPlugin;
|
||||
import info.nightscout.androidaps.plugins.source.NSClientSourcePlugin;
|
||||
import info.nightscout.androidaps.plugins.source.PoctechPlugin;
|
||||
import info.nightscout.androidaps.plugins.source.RandomBgPlugin;
|
||||
import info.nightscout.androidaps.plugins.source.TomatoPlugin;
|
||||
import info.nightscout.androidaps.plugins.source.XdripPlugin;
|
||||
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
|
||||
import info.nightscout.androidaps.receivers.BTReceiver;
|
||||
import info.nightscout.androidaps.receivers.ChargingStateReceiver;
|
||||
import info.nightscout.androidaps.receivers.DataReceiver;
|
||||
import info.nightscout.androidaps.receivers.KeepAliveReceiver;
|
||||
|
@ -130,60 +83,9 @@ public class MainApp extends DaggerApplication {
|
|||
@Inject SP sp;
|
||||
@Inject ProfileFunction profileFunction;
|
||||
|
||||
@Inject ActionsPlugin actionsPlugin;
|
||||
@Inject AutomationPlugin automationPlugin;
|
||||
@Inject ComboPlugin comboPlugin;
|
||||
@Inject CareportalPlugin careportalPlugin;
|
||||
@Inject ConfigBuilderPlugin configBuilderPlugin;
|
||||
@Inject DanaRPlugin danaRPlugin;
|
||||
@Inject DanaRSPlugin danaRSPlugin;
|
||||
@Inject DanaRv2Plugin danaRv2Plugin;
|
||||
@Inject DanaRKoreanPlugin danaRKoreanPlugin;
|
||||
@Inject DataBroadcastPlugin dataBroadcastPlugin;
|
||||
@Inject DstHelperPlugin dstHelperPlugin;
|
||||
@Inject FoodPlugin foodPlugin;
|
||||
@Inject InsulinOrefFreePeakPlugin insulinOrefFreePeakPlugin;
|
||||
@Inject InsulinOrefRapidActingPlugin insulinOrefRapidActingPlugin;
|
||||
@Inject InsulinOrefUltraRapidActingPlugin insulinOrefUltraRapidActingPlugin;
|
||||
@Inject IobCobCalculatorPlugin iobCobCalculatorPlugin;
|
||||
@Inject LocalInsightPlugin localInsightPlugin;
|
||||
@Inject LocalProfilePlugin localProfilePlugin;
|
||||
@Inject LoopPlugin loopPlugin;
|
||||
@Inject MedtronicPumpPlugin medtronicPumpPlugin;
|
||||
@Inject MDIPlugin mdiPlugin;
|
||||
@Inject NSProfilePlugin nsProfilePlugin;
|
||||
@Inject ObjectivesPlugin objectivesPlugin;
|
||||
@Inject SafetyPlugin safetyPlugin;
|
||||
@Inject SmsCommunicatorPlugin smsCommunicatorPlugin;
|
||||
@Inject OpenAPSMAPlugin openAPSMAPlugin;
|
||||
@Inject OpenAPSAMAPlugin openAPSAMAPlugin;
|
||||
@Inject OpenAPSSMBPlugin openAPSSMBPlugin;
|
||||
@Inject OverviewPlugin overviewPlugin;
|
||||
@Inject PersistentNotificationPlugin persistentNotificationPlugin;
|
||||
@Inject RandomBgPlugin randomBgPlugin;
|
||||
@Inject SensitivityOref1Plugin sensitivityOref1Plugin;
|
||||
@Inject SensitivityAAPSPlugin sensitivityAAPSPlugin;
|
||||
@Inject SensitivityOref0Plugin sensitivityOref0Plugin;
|
||||
@Inject SensitivityWeightedAveragePlugin sensitivityWeightedAveragePlugin;
|
||||
@Inject SignatureVerifierPlugin signatureVerifierPlugin;
|
||||
@Inject StorageConstraintPlugin storageConstraintPlugin;
|
||||
@Inject DexcomPlugin dexcomPlugin;
|
||||
@Inject EversensePlugin eversensePlugin;
|
||||
@Inject GlimpPlugin glimpPlugin;
|
||||
@Inject MaintenancePlugin maintenancePlugin;
|
||||
@Inject MM640gPlugin mM640GPlugin;
|
||||
@Inject NSClientPlugin nsClientPlugin;
|
||||
@Inject NSClientSourcePlugin nSClientSourcePlugin;
|
||||
@Inject PoctechPlugin poctechPlugin;
|
||||
@Inject TomatoPlugin tomatoPlugin;
|
||||
@Inject XdripPlugin xdripPlugin;
|
||||
@Inject StatusLinePlugin statusLinePlugin;
|
||||
@Inject TidepoolPlugin tidepoolPlugin;
|
||||
@Inject TreatmentsPlugin treatmentsPlugin;
|
||||
@Inject VirtualPumpPlugin virtualPumpPlugin;
|
||||
@Inject VersionCheckerPlugin versionCheckerPlugin;
|
||||
@Inject WearPlugin wearPlugin;
|
||||
@Inject KeepAliveReceiver.KeepAliveManager keepAliveManager;
|
||||
@Inject List<PluginBase> plugins;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
|
@ -196,15 +98,13 @@ public class MainApp extends DaggerApplication {
|
|||
generateEmptyNotification();
|
||||
sDatabaseHelper = OpenHelperManager.getHelper(sInstance, DatabaseHelper.class);
|
||||
|
||||
/* TODO: put back
|
||||
Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> {
|
||||
if (ex instanceof InternalError) {
|
||||
// usually the app trying to spawn a thread while being killed
|
||||
return;
|
||||
}
|
||||
log.error("Uncaught exception crashing app", ex);
|
||||
aapsLogger.error("Uncaught exception crashing app", ex);
|
||||
});
|
||||
*/
|
||||
|
||||
try {
|
||||
if (fabricPrivacy.fabricEnabled()) {
|
||||
|
@ -231,62 +131,7 @@ public class MainApp extends DaggerApplication {
|
|||
versionCheckersUtils.triggerCheckVersion();
|
||||
|
||||
// Register all tabs in app here
|
||||
pluginStore.add(overviewPlugin);
|
||||
pluginStore.add(iobCobCalculatorPlugin);
|
||||
if (!Config.NSCLIENT) pluginStore.add(actionsPlugin);
|
||||
pluginStore.add(insulinOrefRapidActingPlugin);
|
||||
pluginStore.add(insulinOrefUltraRapidActingPlugin);
|
||||
pluginStore.add(insulinOrefFreePeakPlugin);
|
||||
pluginStore.add(sensitivityOref0Plugin);
|
||||
pluginStore.add(sensitivityAAPSPlugin);
|
||||
pluginStore.add(sensitivityWeightedAveragePlugin);
|
||||
pluginStore.add(sensitivityOref1Plugin);
|
||||
if (Config.PUMPDRIVERS) pluginStore.add(danaRPlugin);
|
||||
if (Config.PUMPDRIVERS) pluginStore.add(danaRKoreanPlugin);
|
||||
if (Config.PUMPDRIVERS) pluginStore.add(danaRv2Plugin);
|
||||
if (Config.PUMPDRIVERS) pluginStore.add(danaRSPlugin);
|
||||
if (Config.PUMPDRIVERS) pluginStore.add(localInsightPlugin);
|
||||
if (Config.PUMPDRIVERS) pluginStore.add(comboPlugin);
|
||||
if (Config.PUMPDRIVERS) pluginStore.add(medtronicPumpPlugin);
|
||||
if (!Config.NSCLIENT) pluginStore.add(mdiPlugin);
|
||||
if (!Config.NSCLIENT) pluginStore.add(virtualPumpPlugin);
|
||||
if (Config.NSCLIENT) pluginStore.add(careportalPlugin);
|
||||
if (Config.APS) pluginStore.add(loopPlugin);
|
||||
if (Config.APS) pluginStore.add(openAPSMAPlugin);
|
||||
if (Config.APS) pluginStore.add(openAPSAMAPlugin);
|
||||
if (Config.APS) pluginStore.add(openAPSSMBPlugin);
|
||||
pluginStore.add(nsProfilePlugin);
|
||||
if (!Config.NSCLIENT) pluginStore.add(localProfilePlugin);
|
||||
pluginStore.add(treatmentsPlugin);
|
||||
if (!Config.NSCLIENT) pluginStore.add(safetyPlugin);
|
||||
if (!Config.NSCLIENT) pluginStore.add(versionCheckerPlugin);
|
||||
if (Config.APS) pluginStore.add(storageConstraintPlugin);
|
||||
if (Config.APS) pluginStore.add(signatureVerifierPlugin);
|
||||
if (Config.APS) pluginStore.add(objectivesPlugin);
|
||||
pluginStore.add(xdripPlugin);
|
||||
pluginStore.add(nSClientSourcePlugin);
|
||||
pluginStore.add(mM640GPlugin);
|
||||
pluginStore.add(glimpPlugin);
|
||||
pluginStore.add(dexcomPlugin);
|
||||
pluginStore.add(poctechPlugin);
|
||||
pluginStore.add(tomatoPlugin);
|
||||
pluginStore.add(eversensePlugin);
|
||||
pluginStore.add(randomBgPlugin);
|
||||
if (!Config.NSCLIENT) pluginStore.add(smsCommunicatorPlugin);
|
||||
pluginStore.add(foodPlugin);
|
||||
|
||||
pluginStore.add(wearPlugin);
|
||||
pluginStore.add(statusLinePlugin);
|
||||
pluginStore.add(persistentNotificationPlugin);
|
||||
pluginStore.add(nsClientPlugin);
|
||||
// if (engineeringMode) pluginsList.add(tidepoolPlugin);
|
||||
pluginStore.add(maintenancePlugin);
|
||||
pluginStore.add(automationPlugin);
|
||||
pluginStore.add(dstHelperPlugin);
|
||||
pluginStore.add(dataBroadcastPlugin);
|
||||
|
||||
pluginStore.add(configBuilderPlugin);
|
||||
|
||||
pluginStore.setPlugins(plugins);
|
||||
configBuilderPlugin.initialize();
|
||||
|
||||
NSUpload.uploadAppStart();
|
||||
|
@ -356,6 +201,11 @@ public class MainApp extends DaggerApplication {
|
|||
filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
|
||||
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
|
||||
registerReceiver(new ChargingStateReceiver(), filter);
|
||||
|
||||
filter = new IntentFilter();
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
||||
registerReceiver(new BTReceiver(), filter);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
|
|
|
@ -285,8 +285,15 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
|
|||
}
|
||||
}
|
||||
|
||||
val hmacPasswords = arrayOf(
|
||||
resourceHelper.gs(R.string.key_bolus_password),
|
||||
resourceHelper.gs(R.string.key_master_password),
|
||||
resourceHelper.gs(R.string.key_application_password),
|
||||
resourceHelper.gs(R.string.key_settings_password)
|
||||
)
|
||||
|
||||
if (pref is Preference) {
|
||||
if ((pref.key != null) && (pref.key.contains("_password"))) {
|
||||
if ((pref.key != null) && (hmacPasswords.contains(pref.key))) {
|
||||
if (sp.getString(pref.key, "").startsWith("hmac:")) {
|
||||
pref.summary = "******"
|
||||
} else {
|
||||
|
|
|
@ -95,6 +95,7 @@ interface AppComponent : AndroidInjector<MainApp> {
|
|||
fun injectTrigger(triggerAutosensValue: TriggerAutosensValue)
|
||||
fun injectTrigger(triggerBg: TriggerBg)
|
||||
fun injectTrigger(triggerBolusAgo: TriggerBolusAgo)
|
||||
fun injectTrigger(triggerBTDevice: TriggerBTDevice)
|
||||
fun injectTrigger(triggerCOB: TriggerCOB)
|
||||
fun injectTrigger(triggerConnector: TriggerConnector)
|
||||
fun injectTrigger(triggerDelta: TriggerDelta)
|
||||
|
@ -127,8 +128,10 @@ interface AppComponent : AndroidInjector<MainApp> {
|
|||
fun injectElement(inputButton: InputButton)
|
||||
fun injectElement(comparator: Comparator)
|
||||
fun injectElement(comparatorExists: ComparatorExists)
|
||||
fun injectElement(comparatorConnect: ComparatorConnect)
|
||||
fun injectElement(inputDateTime: InputDateTime)
|
||||
fun injectElement(inputDelta: InputDelta)
|
||||
fun injectElement(inputDropdownMenu: InputDropdownMenu)
|
||||
fun injectElement(inputDouble: InputDouble)
|
||||
fun injectElement(inputDuration: InputDuration)
|
||||
fun injectElement(inputInsulin: InputInsulin)
|
||||
|
|
|
@ -3,10 +3,12 @@ package info.nightscout.androidaps.dependencyInjection
|
|||
import android.content.Context
|
||||
import androidx.preference.PreferenceManager
|
||||
import dagger.Binds
|
||||
import dagger.Lazy
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.androidaps.Config
|
||||
import info.nightscout.androidaps.MainApp
|
||||
import info.nightscout.androidaps.data.Profile
|
||||
import info.nightscout.androidaps.data.ProfileStore
|
||||
|
@ -15,6 +17,7 @@ import info.nightscout.androidaps.db.BgReading
|
|||
import info.nightscout.androidaps.db.ProfileSwitch
|
||||
import info.nightscout.androidaps.interfaces.ActivePluginProvider
|
||||
import info.nightscout.androidaps.interfaces.CommandQueueProvider
|
||||
import info.nightscout.androidaps.interfaces.PluginBase
|
||||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
import info.nightscout.androidaps.logging.AAPSLoggerProduction
|
||||
import info.nightscout.androidaps.plugins.aps.loop.APSResult
|
||||
|
@ -23,7 +26,6 @@ import info.nightscout.androidaps.plugins.aps.openAPSMA.DetermineBasalResultMA
|
|||
import info.nightscout.androidaps.plugins.aps.openAPSMA.LoggerCallback
|
||||
import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalAdapterSMBJS
|
||||
import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB
|
||||
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
|
||||
import info.nightscout.androidaps.plugins.configBuilder.PluginStore
|
||||
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction
|
||||
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctionImplementation
|
||||
|
@ -33,6 +35,9 @@ import info.nightscout.androidaps.plugins.general.automation.actions.*
|
|||
import info.nightscout.androidaps.plugins.general.automation.elements.*
|
||||
import info.nightscout.androidaps.plugins.general.automation.triggers.*
|
||||
import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.ClassicPrefsFormat
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.EncryptedPrefsFormat
|
||||
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction
|
||||
import info.nightscout.androidaps.plugins.general.smsCommunicator.AuthRequest
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensData
|
||||
|
@ -42,7 +47,6 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobThread
|
|||
import info.nightscout.androidaps.plugins.treatments.Treatment
|
||||
import info.nightscout.androidaps.queue.CommandQueue
|
||||
import info.nightscout.androidaps.queue.commands.*
|
||||
import info.nightscout.androidaps.setupwizard.SWDefinition
|
||||
import info.nightscout.androidaps.setupwizard.SWEventListener
|
||||
import info.nightscout.androidaps.setupwizard.SWScreen
|
||||
import info.nightscout.androidaps.setupwizard.elements.*
|
||||
|
@ -51,11 +55,13 @@ import info.nightscout.androidaps.utils.resources.ResourceHelper
|
|||
import info.nightscout.androidaps.utils.resources.ResourceHelperImplementation
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SPImplementation
|
||||
import info.nightscout.androidaps.utils.storage.FileStorage
|
||||
import info.nightscout.androidaps.utils.storage.Storage
|
||||
import info.nightscout.androidaps.utils.wizard.BolusWizard
|
||||
import info.nightscout.androidaps.utils.wizard.QuickWizardEntry
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module(includes = [AppModule.AppBindings::class])
|
||||
@Module(includes = [AppModule.AppBindings::class, PluginsModule::class])
|
||||
open class AppModule {
|
||||
|
||||
@Provides
|
||||
|
@ -88,6 +94,24 @@ open class AppModule {
|
|||
*/
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun providesPlugins(@PluginsModule.AllConfigs allConfigs: Map<@JvmSuppressWildcards Int, @JvmSuppressWildcards PluginBase>,
|
||||
@PluginsModule.PumpDriver pumpDrivers: Lazy<Map<@JvmSuppressWildcards Int, @JvmSuppressWildcards PluginBase>>,
|
||||
@PluginsModule.NotNSClient notNsClient: Lazy<Map<@JvmSuppressWildcards Int, @JvmSuppressWildcards PluginBase>>,
|
||||
@PluginsModule.APS aps: Lazy<Map<@JvmSuppressWildcards Int, @JvmSuppressWildcards PluginBase>>): List<@JvmSuppressWildcards PluginBase> {
|
||||
val plugins = allConfigs.toMutableMap()
|
||||
if (Config.PUMPDRIVERS) plugins += pumpDrivers.get()
|
||||
if (Config.APS) plugins += aps.get()
|
||||
if (!Config.NSCLIENT) plugins += notNsClient.get()
|
||||
return plugins.toList().sortedBy { it.first }.map { it.second }
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStorage(): Storage {
|
||||
return FileStorage()
|
||||
}
|
||||
|
||||
@Module
|
||||
interface AppBindings {
|
||||
|
||||
|
@ -156,6 +180,7 @@ open class AppModule {
|
|||
@ContributesAndroidInjector
|
||||
fun triggerPumpLastConnectionInjector(): TriggerPumpLastConnection
|
||||
|
||||
@ContributesAndroidInjector fun triggerBTDeviceInjector(): TriggerBTDevice
|
||||
@ContributesAndroidInjector fun triggerRecurringTimeInjector(): TriggerRecurringTime
|
||||
@ContributesAndroidInjector fun triggerTempTargetInjector(): TriggerTempTarget
|
||||
@ContributesAndroidInjector fun triggerTime(): TriggerTime
|
||||
|
@ -182,10 +207,12 @@ open class AppModule {
|
|||
@ContributesAndroidInjector fun inputBgInjector(): InputBg
|
||||
@ContributesAndroidInjector fun inputButtonInjector(): InputButton
|
||||
@ContributesAndroidInjector fun comparatorInjector(): Comparator
|
||||
@ContributesAndroidInjector fun comparatorConnectInjector(): ComparatorConnect
|
||||
@ContributesAndroidInjector fun comparatorExistsInjector(): ComparatorExists
|
||||
@ContributesAndroidInjector fun inputDateTimeInjector(): InputDateTime
|
||||
@ContributesAndroidInjector fun inputDeltaInjector(): InputDelta
|
||||
@ContributesAndroidInjector fun inputDoubleInjector(): InputDouble
|
||||
@ContributesAndroidInjector fun inputDropdownMenuInjector(): InputDropdownMenu
|
||||
@ContributesAndroidInjector fun inputDurationInjector(): InputDuration
|
||||
@ContributesAndroidInjector fun inputInsulinInjector(): InputInsulin
|
||||
@ContributesAndroidInjector fun inputLocationModeInjector(): InputLocationMode
|
||||
|
@ -234,6 +261,10 @@ open class AppModule {
|
|||
|
||||
@ContributesAndroidInjector fun graphDataInjector(): GraphData
|
||||
|
||||
@ContributesAndroidInjector fun importExportPrefsInjector(): ImportExportPrefs
|
||||
@ContributesAndroidInjector fun encryptedPrefsFormatInjector(): EncryptedPrefsFormat
|
||||
@ContributesAndroidInjector fun classicPrefsFormatInjector(): ClassicPrefsFormat
|
||||
|
||||
@Binds fun bindContext(mainApp: MainApp): Context
|
||||
@Binds fun bindInjector(mainApp: MainApp): HasAndroidInjector
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpFragment
|
|||
import info.nightscout.androidaps.plugins.source.BGSourceFragment
|
||||
import info.nightscout.androidaps.plugins.treatments.TreatmentsFragment
|
||||
import info.nightscout.androidaps.plugins.treatments.fragments.*
|
||||
import info.nightscout.androidaps.utils.protection.PasswordCheck
|
||||
|
||||
@Module
|
||||
@Suppress("unused")
|
||||
|
@ -114,4 +115,6 @@ abstract class FragmentsModule {
|
|||
@ContributesAndroidInjector abstract fun contributesTreatmentDialog(): TreatmentDialog
|
||||
@ContributesAndroidInjector abstract fun contributesWizardDialog(): WizardDialog
|
||||
@ContributesAndroidInjector abstract fun contributesWizardInfoDialog(): WizardInfoDialog
|
||||
|
||||
@ContributesAndroidInjector abstract fun contributesPasswordCheck(): PasswordCheck
|
||||
}
|
|
@ -0,0 +1,379 @@
|
|||
package info.nightscout.androidaps.dependencyInjection
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Lazy
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import dagger.multibindings.IntoSet
|
||||
import info.nightscout.androidaps.Config
|
||||
import info.nightscout.androidaps.interfaces.PluginBase
|
||||
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
|
||||
import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin
|
||||
import info.nightscout.androidaps.plugins.aps.openAPSMA.OpenAPSMAPlugin
|
||||
import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin
|
||||
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
|
||||
import info.nightscout.androidaps.plugins.constraints.dstHelper.DstHelperPlugin
|
||||
import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin
|
||||
import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin
|
||||
import info.nightscout.androidaps.plugins.constraints.signatureVerifier.SignatureVerifierPlugin
|
||||
import info.nightscout.androidaps.plugins.constraints.storage.StorageConstraintPlugin
|
||||
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerPlugin
|
||||
import info.nightscout.androidaps.plugins.general.actions.ActionsPlugin
|
||||
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
|
||||
import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin
|
||||
import info.nightscout.androidaps.plugins.general.dataBroadcaster.DataBroadcastPlugin
|
||||
import info.nightscout.androidaps.plugins.general.food.FoodPlugin
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin
|
||||
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin
|
||||
import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin
|
||||
import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin
|
||||
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
|
||||
import info.nightscout.androidaps.plugins.general.wear.WearPlugin
|
||||
import info.nightscout.androidaps.plugins.general.xdripStatusline.StatusLinePlugin
|
||||
import info.nightscout.androidaps.plugins.insulin.InsulinOrefFreePeakPlugin
|
||||
import info.nightscout.androidaps.plugins.insulin.InsulinOrefRapidActingPlugin
|
||||
import info.nightscout.androidaps.plugins.insulin.InsulinOrefUltraRapidActingPlugin
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
|
||||
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
|
||||
import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin
|
||||
import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin
|
||||
import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin
|
||||
import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin
|
||||
import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin
|
||||
import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin
|
||||
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin
|
||||
import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin
|
||||
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
|
||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin
|
||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin
|
||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin
|
||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin
|
||||
import info.nightscout.androidaps.plugins.source.*
|
||||
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Module
|
||||
abstract class PluginsModule {
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(0)
|
||||
abstract fun bindOverviewPlugin(plugin: OverviewPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(10)
|
||||
abstract fun bindIobCobCalculatorPlugin(plugin: IobCobCalculatorPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@NotNSClient
|
||||
@IntoMap
|
||||
@IntKey(20)
|
||||
abstract fun bindActionsPlugin(plugin: ActionsPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(30)
|
||||
abstract fun bindInsulinOrefRapidActingPlugin(plugin: InsulinOrefRapidActingPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(40)
|
||||
abstract fun bindInsulinOrefUltraRapidActingPlugin(plugin: InsulinOrefUltraRapidActingPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(50)
|
||||
abstract fun bindInsulinOrefFreePeakPlugin(plugin: InsulinOrefFreePeakPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(60)
|
||||
abstract fun bindSensitivityOref0Plugin(plugin: SensitivityOref0Plugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(70)
|
||||
abstract fun bindSensitivityAAPSPlugin(plugin: SensitivityAAPSPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(80)
|
||||
abstract fun bindSensitivityWeightedAveragePlugin(plugin: SensitivityWeightedAveragePlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(90)
|
||||
abstract fun bindSensitivityOref1Plugin(plugin: SensitivityOref1Plugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@PumpDriver
|
||||
@IntoMap
|
||||
@IntKey(100)
|
||||
abstract fun bindDanaRPlugin(plugin: DanaRPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@PumpDriver
|
||||
@IntoMap
|
||||
@IntKey(110)
|
||||
abstract fun bindDanaRKoreanPlugin(plugin: DanaRKoreanPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@PumpDriver
|
||||
@IntoMap
|
||||
@IntKey(120)
|
||||
abstract fun bindDanaRv2Plugin(plugin: DanaRv2Plugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@PumpDriver
|
||||
@IntoMap
|
||||
@IntKey(130)
|
||||
abstract fun bindDanaRSPlugin(plugin: DanaRSPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@PumpDriver
|
||||
@IntoMap
|
||||
@IntKey(140)
|
||||
abstract fun bindLocalInsightPlugin(plugin: LocalInsightPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@PumpDriver
|
||||
@IntoMap
|
||||
@IntKey(150)
|
||||
abstract fun bindComboPlugin(plugin: ComboPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@PumpDriver
|
||||
@IntoMap
|
||||
@IntKey(160)
|
||||
abstract fun bindMedtronicPumpPlugin(plugin: MedtronicPumpPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@NotNSClient
|
||||
@IntoMap
|
||||
@IntKey(170)
|
||||
abstract fun bindMDIPlugin(plugin: MDIPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@NotNSClient
|
||||
@IntoMap
|
||||
@IntKey(180)
|
||||
abstract fun bindVirtualPumpPlugin(plugin: VirtualPumpPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@NotNSClient
|
||||
@IntoMap
|
||||
@IntKey(190)
|
||||
abstract fun bindCareportalPlugin(plugin: CareportalPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@APS
|
||||
@IntoMap
|
||||
@IntKey(200)
|
||||
abstract fun bindLoopPlugin(plugin: LoopPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@APS
|
||||
@IntoMap
|
||||
@IntKey(210)
|
||||
abstract fun bindOpenAPSMAPlugin(plugin: OpenAPSMAPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@APS
|
||||
@IntoMap
|
||||
@IntKey(220)
|
||||
abstract fun bindOpenAPSAMAPlugin(plugin: OpenAPSAMAPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@APS
|
||||
@IntoMap
|
||||
@IntKey(230)
|
||||
abstract fun bindOpenAPSSMBPlugin(plugin: OpenAPSSMBPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(240)
|
||||
abstract fun bindNSProfilePlugin(plugin: NSProfilePlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@NotNSClient
|
||||
@IntoMap
|
||||
@IntKey(250)
|
||||
abstract fun bindLocalProfilePlugin(plugin: LocalProfilePlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(255)
|
||||
abstract fun bindAutomationPlugin(plugin: AutomationPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(260)
|
||||
abstract fun bindTreatmentsPlugin(plugin: TreatmentsPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@NotNSClient
|
||||
@IntoSet
|
||||
abstract fun bindSafetyPlugin(plugin: SafetyPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@NotNSClient
|
||||
@IntoMap
|
||||
@IntKey(270)
|
||||
abstract fun bindVersionCheckerPlugin(plugin: VersionCheckerPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@NotNSClient
|
||||
@IntoMap
|
||||
@IntKey(280)
|
||||
abstract fun bindSmsCommunicatorPlugin(plugin: SmsCommunicatorPlugin): PluginBase
|
||||
|
||||
|
||||
@Binds
|
||||
@APS
|
||||
@IntoMap
|
||||
@IntKey(290)
|
||||
abstract fun bindStorageConstraintPlugin(plugin: StorageConstraintPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@APS
|
||||
@IntoMap
|
||||
@IntKey(300)
|
||||
abstract fun bindSignatureVerifierPlugin(plugin: SignatureVerifierPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@APS
|
||||
@IntoMap
|
||||
@IntKey(310)
|
||||
abstract fun bindObjectivesPlugin(plugin: ObjectivesPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(320)
|
||||
abstract fun bindFoodPlugin(plugin: FoodPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(330)
|
||||
abstract fun bindWearPlugin(plugin: WearPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(340)
|
||||
abstract fun bindStatusLinePlugin(plugin: StatusLinePlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(350)
|
||||
abstract fun bindPersistentNotificationPlugin(plugin: PersistentNotificationPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(360)
|
||||
abstract fun bindNSClientPlugin(plugin: NSClientPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(370)
|
||||
abstract fun bindMaintenancePlugin(plugin: MaintenancePlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(380)
|
||||
abstract fun bindDstHelperPlugin(plugin: DstHelperPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(390)
|
||||
abstract fun bindDataBroadcastPlugin(plugin: DataBroadcastPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(400)
|
||||
abstract fun bindXdripPlugin(plugin: XdripPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(410)
|
||||
abstract fun bindNSClientSourcePlugin(plugin: NSClientSourcePlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(420)
|
||||
abstract fun bindMM640gPlugin(plugin: MM640gPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(430)
|
||||
abstract fun bindGlimpPlugin(plugin: GlimpPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(440)
|
||||
abstract fun bindDexcomPlugin(plugin: DexcomPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(450)
|
||||
abstract fun bindPoctechPlugin(plugin: PoctechPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(460)
|
||||
abstract fun bindTomatoPlugin(plugin: TomatoPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(470)
|
||||
abstract fun bindRandomBgPlugin(plugin: RandomBgPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(480)
|
||||
abstract fun bindConfigBuilderPlugin(plugin: ConfigBuilderPlugin): PluginBase
|
||||
|
||||
@Qualifier
|
||||
annotation class AllConfigs
|
||||
|
||||
@Qualifier
|
||||
annotation class PumpDriver
|
||||
|
||||
@Qualifier
|
||||
annotation class NotNSClient
|
||||
|
||||
@Qualifier
|
||||
annotation class APS
|
||||
|
||||
}
|
|
@ -3,16 +3,13 @@ package info.nightscout.androidaps.dependencyInjection
|
|||
import dagger.Module
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkBluetoothStateReceiver
|
||||
import info.nightscout.androidaps.receivers.ChargingStateReceiver
|
||||
import info.nightscout.androidaps.receivers.DataReceiver
|
||||
import info.nightscout.androidaps.receivers.KeepAliveReceiver
|
||||
import info.nightscout.androidaps.receivers.NetworkChangeReceiver
|
||||
import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver
|
||||
import info.nightscout.androidaps.receivers.*
|
||||
|
||||
@Module
|
||||
@Suppress("unused")
|
||||
abstract class ReceiversModule {
|
||||
|
||||
@ContributesAndroidInjector abstract fun contributesBTReceiver(): BTReceiver
|
||||
@ContributesAndroidInjector abstract fun contributesChargingStateReceiver(): ChargingStateReceiver
|
||||
@ContributesAndroidInjector abstract fun contributesDataReceiver(): DataReceiver
|
||||
@ContributesAndroidInjector abstract fun contributesKeepAliveReceiver(): KeepAliveReceiver
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package info.nightscout.androidaps.events
|
||||
|
||||
class EventBTChange constructor(val state: Change, val deviceName: String) : Event() {
|
||||
enum class Change {
|
||||
CONNECT,
|
||||
DISCONNECT
|
||||
}
|
||||
}
|
|
@ -303,7 +303,7 @@ public class HistoryBrowseActivity extends NoSplashAppCompatActivity {
|
|||
|
||||
// add basal data
|
||||
if (pump.getPumpDescription().isTempBasalCapable && showBasal) {
|
||||
graphData.addBasals(fromTime, toTime, lowLine / graphData.maxY / 1.2d);
|
||||
graphData.addBasals(fromTime, toTime, lowLine / graphData.getMaxY() / 1.2d);
|
||||
}
|
||||
|
||||
// **** NOW line ****
|
||||
|
|
|
@ -6,6 +6,7 @@ import info.nightscout.androidaps.logging.LTag
|
|||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
@Singleton
|
||||
class PluginStore @Inject constructor(
|
||||
|
@ -26,8 +27,7 @@ class PluginStore @Inject constructor(
|
|||
return pluginStore!!
|
||||
}
|
||||
}
|
||||
|
||||
var plugins = ArrayList<PluginBase>()
|
||||
lateinit var plugins: List<@JvmSuppressWildcards PluginBase>
|
||||
|
||||
private var activeBgSource: BgSourceInterface? = null
|
||||
private var activePump: PumpInterface? = null
|
||||
|
@ -41,11 +41,6 @@ class PluginStore @Inject constructor(
|
|||
verifySelectionInCategories()
|
||||
}
|
||||
|
||||
fun add(pluginBase: PluginBase): ActivePluginProvider {
|
||||
plugins.add(pluginBase)
|
||||
return this
|
||||
}
|
||||
|
||||
fun getDefaultPlugin(type: PluginType): PluginBase {
|
||||
for (p in plugins)
|
||||
if (p.getType() == type && p.isDefault()) return p
|
||||
|
@ -243,6 +238,6 @@ class PluginStore @Inject constructor(
|
|||
override fun getActiveTreatments(): TreatmentsInterface =
|
||||
activeTreatments ?: checkNotNull(activeTreatments) { "No treatments selected" }
|
||||
|
||||
override fun getPluginsList(): ArrayList<PluginBase> = plugins
|
||||
override fun getPluginsList(): ArrayList<PluginBase> = ArrayList(plugins)
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@ import android.os.Build
|
|||
import android.os.Handler
|
||||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.events.EventBTChange
|
||||
import info.nightscout.androidaps.events.EventChargingState
|
||||
import info.nightscout.androidaps.events.EventLocationChange
|
||||
import info.nightscout.androidaps.events.EventNetworkChange
|
||||
|
@ -38,6 +39,7 @@ import org.json.JSONObject
|
|||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
@Singleton
|
||||
class AutomationPlugin @Inject constructor(
|
||||
|
@ -65,6 +67,7 @@ class AutomationPlugin @Inject constructor(
|
|||
|
||||
val automationEvents = ArrayList<AutomationEvent>()
|
||||
var executionLog: MutableList<String> = ArrayList()
|
||||
var btConnects : MutableList<EventBTChange> = ArrayList()
|
||||
|
||||
private val loopHandler = Handler()
|
||||
private lateinit var refreshLoop: Runnable
|
||||
|
@ -123,6 +126,14 @@ class AutomationPlugin @Inject constructor(
|
|||
.toObservable(EventAutosensCalculationFinished::class.java)
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe({ processActions() }, { fabricPrivacy.logException(it) })
|
||||
disposable += rxBus
|
||||
.toObservable(EventBTChange::class.java)
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe({
|
||||
aapsLogger.debug(LTag.AUTOMATION, "Grabbed new BT event: $it")
|
||||
btConnects.add(it)
|
||||
processActions()
|
||||
}, { fabricPrivacy.logException(it) })
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
|
@ -197,6 +208,12 @@ class AutomationPlugin @Inject constructor(
|
|||
event.lastRun = DateUtil.now()
|
||||
}
|
||||
}
|
||||
// we cannot detect connected BT devices
|
||||
// so let's collect all connection/disconnections between 2 runs of processActions()
|
||||
// TriggerBTDevice can pick up and process these events
|
||||
// after processing clear events to prevent repeated actions
|
||||
btConnects.clear()
|
||||
|
||||
storeToSP() // save last run time
|
||||
}
|
||||
|
||||
|
@ -231,8 +248,8 @@ class AutomationPlugin @Inject constructor(
|
|||
TriggerLocation(injector),
|
||||
TriggerAutosensValue(injector),
|
||||
TriggerBolusAgo(injector),
|
||||
TriggerPumpLastConnection(injector)
|
||||
TriggerPumpLastConnection(injector),
|
||||
TriggerBTDevice(injector)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuil
|
|||
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload
|
||||
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
|
||||
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
|
||||
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationUserMessage
|
||||
import info.nightscout.androidaps.queue.Callback
|
||||
import info.nightscout.androidaps.utils.JsonHelper
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
|
@ -30,7 +31,7 @@ class ActionNotification(injector: HasAndroidInjector) : Action(injector) {
|
|||
@DrawableRes override fun icon(): Int = R.drawable.ic_notifications
|
||||
|
||||
override fun doAction(callback: Callback) {
|
||||
val notification = Notification(Notification.USERMESSAGE, text.value, Notification.URGENT)
|
||||
val notification = NotificationUserMessage(text.value)
|
||||
rxBus.send(EventNewNotification(notification))
|
||||
NSUpload.uploadError(text.value)
|
||||
rxBus.send(EventRefreshOverview("ActionNotification"))
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package info.nightscout.androidaps.plugins.general.automation.elements
|
||||
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Spinner
|
||||
import androidx.annotation.StringRes
|
||||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class ComparatorConnect(injector: HasAndroidInjector) : Element(injector) {
|
||||
@Inject lateinit var resourceHelper: ResourceHelper
|
||||
|
||||
enum class Compare {
|
||||
ON_CONNECT, ON_DISCONNECT;
|
||||
|
||||
@get:StringRes val stringRes: Int
|
||||
get() = when (this) {
|
||||
ON_CONNECT -> R.string.onconnect
|
||||
ON_DISCONNECT -> R.string.ondisconnect
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun labels(resourceHelper: ResourceHelper): List<String> {
|
||||
val list: MutableList<String> = ArrayList()
|
||||
for (c in values()) list.add(resourceHelper.gs(c.stringRes))
|
||||
return list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(injector: HasAndroidInjector, value: Compare) : this(injector) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
var value = Compare.ON_CONNECT
|
||||
|
||||
override fun addToLayout(root: LinearLayout) {
|
||||
val spinner = Spinner(root.context)
|
||||
val spinnerArrayAdapter = ArrayAdapter(root.context, R.layout.spinner_centered, Compare.labels(resourceHelper))
|
||||
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
spinner.adapter = spinnerArrayAdapter
|
||||
val spinnerParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
|
||||
spinnerParams.setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4))
|
||||
spinner.layoutParams = spinnerParams
|
||||
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
|
||||
value = Compare.values()[position]
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
}
|
||||
spinner.setSelection(value.ordinal)
|
||||
root.addView(spinner)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package info.nightscout.androidaps.plugins.general.automation.elements
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Spinner
|
||||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class InputDropdownMenu(injector: HasAndroidInjector) : Element(injector) {
|
||||
@Inject lateinit var resourceHelper: ResourceHelper
|
||||
|
||||
private var itemList: ArrayList<CharSequence> = ArrayList()
|
||||
var value: String = ""
|
||||
|
||||
constructor(injector: HasAndroidInjector, name: String) : this(injector) {
|
||||
value = name
|
||||
}
|
||||
|
||||
constructor(injector: HasAndroidInjector, another: InputDropdownMenu) : this(injector) {
|
||||
value = another.value
|
||||
}
|
||||
|
||||
override fun addToLayout(root: LinearLayout) {
|
||||
val spinner = Spinner(root.context)
|
||||
spinner.adapter = ArrayAdapter(root.context,
|
||||
R.layout.spinner_centered, itemList).also {
|
||||
it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
}
|
||||
spinner.layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
).also { it.setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4)) }
|
||||
|
||||
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
|
||||
setValue(itemList[position].toString())
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
}
|
||||
spinner.setSelection(0)
|
||||
root.addView(LinearLayout(root.context).also {
|
||||
it.orientation = LinearLayout.VERTICAL
|
||||
it.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
it.addView(spinner)
|
||||
})
|
||||
}
|
||||
|
||||
fun setValue(name: String): InputDropdownMenu {
|
||||
value = name
|
||||
return this
|
||||
}
|
||||
|
||||
fun setList(values: ArrayList<CharSequence>) {
|
||||
itemList = ArrayList(values)
|
||||
}
|
||||
|
||||
// For testing only
|
||||
fun add(item: String) {
|
||||
itemList.add(item)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package info.nightscout.androidaps.plugins.general.automation.triggers
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.content.Context
|
||||
import android.widget.LinearLayout
|
||||
import com.google.common.base.Optional
|
||||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.events.EventBTChange
|
||||
import info.nightscout.androidaps.logging.LTag
|
||||
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
|
||||
import info.nightscout.androidaps.plugins.general.automation.elements.ComparatorConnect
|
||||
import info.nightscout.androidaps.plugins.general.automation.elements.InputDropdownMenu
|
||||
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder
|
||||
import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel
|
||||
import info.nightscout.androidaps.utils.JsonHelper
|
||||
import org.json.JSONObject
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class TriggerBTDevice(injector: HasAndroidInjector) : Trigger(injector) {
|
||||
@Inject lateinit var context: Context
|
||||
@Inject lateinit var automationPlugin: AutomationPlugin
|
||||
|
||||
var btDevice = InputDropdownMenu(injector, "")
|
||||
var comparator: ComparatorConnect = ComparatorConnect(injector)
|
||||
|
||||
private constructor(injector: HasAndroidInjector, triggerBTDevice: TriggerBTDevice) : this(injector) {
|
||||
comparator = ComparatorConnect(injector, triggerBTDevice.comparator.value)
|
||||
btDevice.value = triggerBTDevice.btDevice.value
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun shouldRun(): Boolean {
|
||||
if (eventExists()) {
|
||||
aapsLogger.debug(LTag.AUTOMATION, "Ready for execution: " + friendlyDescription())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@Synchronized override fun toJSON(): String {
|
||||
val data = JSONObject()
|
||||
.put("comparator", comparator.value.toString())
|
||||
.put("name", btDevice.value)
|
||||
return JSONObject()
|
||||
.put("type", this::class.java.name)
|
||||
.put("data", data)
|
||||
.toString()
|
||||
}
|
||||
|
||||
override fun fromJSON(data: String): Trigger {
|
||||
val d = JSONObject(data)
|
||||
btDevice.value = JsonHelper.safeGetString(d, "name")!!
|
||||
comparator.value = ComparatorConnect.Compare.valueOf(JsonHelper.safeGetString(d, "comparator")!!)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun friendlyName(): Int = R.string.btdevice
|
||||
|
||||
override fun friendlyDescription(): String =
|
||||
resourceHelper.gs(R.string.btdevicecompared, btDevice.value, resourceHelper.gs(comparator.value.stringRes))
|
||||
|
||||
override fun icon(): Optional<Int?> = Optional.of(R.drawable.ic_bluetooth_white_48dp)
|
||||
|
||||
override fun duplicate(): Trigger = TriggerBTDevice(injector, this)
|
||||
|
||||
override fun generateDialog(root: LinearLayout) {
|
||||
val pairedDevices = devicesPaired()
|
||||
btDevice.setList(pairedDevices)
|
||||
LayoutBuilder()
|
||||
.add(StaticLabel(injector, R.string.btdevice, this))
|
||||
.add(btDevice)
|
||||
.add(comparator)
|
||||
.build(root)
|
||||
}
|
||||
|
||||
// Get the list of paired BT devices to use in dropdown menu
|
||||
private fun devicesPaired(): ArrayList<CharSequence> {
|
||||
val s = ArrayList<CharSequence>()
|
||||
BluetoothAdapter.getDefaultAdapter()?.bondedDevices?.forEach { s.add(it.name) }
|
||||
return s
|
||||
}
|
||||
|
||||
private fun eventExists(): Boolean {
|
||||
automationPlugin.btConnects.forEach {
|
||||
if (btDevice.value == it.deviceName) {
|
||||
if (comparator.value == ComparatorConnect.Compare.ON_CONNECT && it.state == EventBTChange.Change.CONNECT) return true
|
||||
if (comparator.value == ComparatorConnect.Compare.ON_DISCONNECT && it.state == EventBTChange.Change.DISCONNECT) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
package info.nightscout.androidaps.plugins.general.maintenance;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Environment;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Map;
|
||||
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.events.EventAppExit;
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.logging.StacktraceLoggerWrapper;
|
||||
import info.nightscout.androidaps.plugins.bus.RxBus;
|
||||
import info.nightscout.androidaps.utils.OKDialog;
|
||||
import info.nightscout.androidaps.utils.SP;
|
||||
import info.nightscout.androidaps.utils.ToastUtils;
|
||||
|
||||
/**
|
||||
* Created by mike on 03.07.2016.
|
||||
*/
|
||||
|
||||
public class ImportExportPrefs {
|
||||
private static Logger log = StacktraceLoggerWrapper.getLogger(L.CORE);
|
||||
private static File path = new File(Environment.getExternalStorageDirectory().toString());
|
||||
static public final File file = new File(path, MainApp.gs(R.string.app_name) + "Preferences");
|
||||
|
||||
private static final int REQUEST_EXTERNAL_STORAGE = 1;
|
||||
private static String[] PERMISSIONS_STORAGE = {
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
};
|
||||
|
||||
static void verifyStoragePermissions(Fragment fragment) {
|
||||
int permission = ContextCompat.checkSelfPermission(fragment.getContext(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
|
||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||
// We don't have permission so prompt the user
|
||||
fragment.requestPermissions(PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void exportSharedPreferences(final Fragment f) {
|
||||
exportSharedPreferences(f.getContext());
|
||||
}
|
||||
|
||||
private static void exportSharedPreferences(final Context context) {
|
||||
OKDialog.showConfirmation(context, MainApp.gs(R.string.maintenance), MainApp.gs(R.string.export_to) + " " + file + " ?", () -> {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
try {
|
||||
FileWriter fw = new FileWriter(file);
|
||||
PrintWriter pw = new PrintWriter(fw);
|
||||
Map<String, ?> prefsMap = prefs.getAll();
|
||||
for (Map.Entry<String, ?> entry : prefsMap.entrySet()) {
|
||||
pw.println(entry.getKey() + "::" + entry.getValue().toString());
|
||||
}
|
||||
pw.close();
|
||||
fw.close();
|
||||
ToastUtils.showToastInUiThread(context, MainApp.gs(R.string.exported));
|
||||
} catch (FileNotFoundException e) {
|
||||
ToastUtils.showToastInUiThread(context, MainApp.gs(R.string.filenotfound) + " " + file);
|
||||
log.error("Unhandled exception", e);
|
||||
} catch (IOException e) {
|
||||
log.error("Unhandled exception", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void importSharedPreferences(final Fragment fragment) {
|
||||
importSharedPreferences(fragment.getContext());
|
||||
}
|
||||
|
||||
public static void importSharedPreferences(final Context context) {
|
||||
OKDialog.showConfirmation(context, MainApp.gs(R.string.maintenance), MainApp.gs(R.string.import_from) + " " + file + " ?", () -> {
|
||||
String line;
|
||||
String[] lineParts;
|
||||
try {
|
||||
SP.clear();
|
||||
|
||||
BufferedReader reader = new BufferedReader(new FileReader(file));
|
||||
while ((line = reader.readLine()) != null) {
|
||||
lineParts = line.split("::");
|
||||
if (lineParts.length == 2) {
|
||||
if (lineParts[1].equals("true") || lineParts[1].equals("false")) {
|
||||
SP.putBoolean(lineParts[0], Boolean.parseBoolean(lineParts[1]));
|
||||
} else {
|
||||
SP.putString(lineParts[0], lineParts[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.close();
|
||||
SP.putBoolean(R.string.key_setupwizard_processed, true);
|
||||
OKDialog.show(context, MainApp.gs(R.string.setting_imported), MainApp.gs(R.string.restartingapp), () -> {
|
||||
log.debug("Exiting");
|
||||
RxBus.Companion.getINSTANCE().send(new EventAppExit());
|
||||
if (context instanceof Activity) {
|
||||
((Activity) context).finish();
|
||||
}
|
||||
System.runFinalization();
|
||||
System.exit(0);
|
||||
});
|
||||
} catch (FileNotFoundException e) {
|
||||
ToastUtils.showToastInUiThread(context, MainApp.gs(R.string.filenotfound) + " " + file);
|
||||
log.error("Unhandled exception", e);
|
||||
} catch (IOException e) {
|
||||
log.error("Unhandled exception", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,340 @@
|
|||
package info.nightscout.androidaps.plugins.general.maintenance
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import info.nightscout.androidaps.BuildConfig
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.activities.PreferencesActivity
|
||||
import info.nightscout.androidaps.events.EventAppExit
|
||||
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.plugins.general.smsCommunicator.otp.OneTimePassword
|
||||
import info.nightscout.androidaps.utils.DateUtil
|
||||
import info.nightscout.androidaps.utils.OKDialog
|
||||
import info.nightscout.androidaps.utils.OKDialog.show
|
||||
import info.nightscout.androidaps.utils.ToastUtils
|
||||
import info.nightscout.androidaps.utils.alertDialogs.PrefImportSummaryDialog
|
||||
import info.nightscout.androidaps.utils.alertDialogs.TwoMessagesAlertDialog
|
||||
import info.nightscout.androidaps.utils.alertDialogs.WarningDialog
|
||||
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
|
||||
import info.nightscout.androidaps.utils.protection.PasswordCheck
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.Days
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Created by mike on 03.07.2016.
|
||||
*/
|
||||
|
||||
private const val REQUEST_EXTERNAL_STORAGE = 1
|
||||
private val PERMISSIONS_STORAGE = arrayOf(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
|
||||
private const val IMPORT_AGE_NOT_YET_OLD_DAYS = 60
|
||||
|
||||
@Singleton
|
||||
class ImportExportPrefs @Inject constructor(
|
||||
private var log: AAPSLogger,
|
||||
private val resourceHelper: ResourceHelper,
|
||||
private val sp: SP,
|
||||
private val buildHelper: BuildHelper,
|
||||
private val otp: OneTimePassword,
|
||||
private val rxBus: RxBusWrapper,
|
||||
private val passwordCheck: PasswordCheck,
|
||||
private val classicPrefsFormat: ClassicPrefsFormat,
|
||||
private val encryptedPrefsFormat: EncryptedPrefsFormat
|
||||
) {
|
||||
|
||||
val TAG = LTag.CORE
|
||||
|
||||
private val path = File(Environment.getExternalStorageDirectory().toString())
|
||||
|
||||
private val file = File(path, resourceHelper.gs(R.string.app_name) + "Preferences")
|
||||
private val encFile = File(path, resourceHelper.gs(R.string.app_name) + "Preferences.json")
|
||||
|
||||
fun prefsImportFile(): File {
|
||||
return if (encFile.exists()) encFile else file
|
||||
}
|
||||
|
||||
fun prefsFileExists(): Boolean {
|
||||
return encFile.exists() || file.exists()
|
||||
}
|
||||
|
||||
|
||||
fun exportSharedPreferences(f: Fragment) {
|
||||
f.activity?.let { exportSharedPreferences(it) }
|
||||
}
|
||||
|
||||
fun verifyStoragePermissions(fragment: Fragment) {
|
||||
fragment.context?.let {
|
||||
val permission = ContextCompat.checkSelfPermission(it,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||
// We don't have permission so prompt the user
|
||||
fragment.requestPermissions(PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareMetadata(context: Context): Map<PrefsMetadataKey, PrefMetadata> {
|
||||
|
||||
val metadata: MutableMap<PrefsMetadataKey, PrefMetadata> = mutableMapOf()
|
||||
|
||||
metadata[PrefsMetadataKey.DEVICE_NAME] = PrefMetadata(detectUserName(context), PrefsStatus.OK)
|
||||
metadata[PrefsMetadataKey.CREATED_AT] = PrefMetadata(DateUtil.toISOString(Date()), PrefsStatus.OK)
|
||||
metadata[PrefsMetadataKey.AAPS_VERSION] = PrefMetadata(BuildConfig.VERSION_NAME, PrefsStatus.OK)
|
||||
metadata[PrefsMetadataKey.AAPS_FLAVOUR] = PrefMetadata(BuildConfig.FLAVOR, PrefsStatus.OK)
|
||||
metadata[PrefsMetadataKey.DEVICE_MODEL] = PrefMetadata(getCurrentDeviceModelString(), PrefsStatus.OK)
|
||||
|
||||
if (prefsEncryptionIsDisabled()) {
|
||||
metadata[PrefsMetadataKey.ENCRYPTION] = PrefMetadata("Disabled", PrefsStatus.DISABLED)
|
||||
} else {
|
||||
metadata[PrefsMetadataKey.ENCRYPTION] = PrefMetadata("Enabled", PrefsStatus.OK)
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
private fun detectUserName(context: Context): String {
|
||||
// based on https://medium.com/@pribble88/how-to-get-an-android-device-nickname-4b4700b3068c
|
||||
val n1 = Settings.System.getString(context.contentResolver, "bluetooth_name")
|
||||
val n2 = Settings.Secure.getString(context.contentResolver, "bluetooth_name")
|
||||
val n3 = BluetoothAdapter.getDefaultAdapter()?.name
|
||||
val n4 = Settings.System.getString(context.contentResolver, "device_name")
|
||||
val n5 = Settings.Secure.getString(context.contentResolver, "lock_screen_owner_info")
|
||||
val n6 = Settings.Global.getString(context.contentResolver, "device_name")
|
||||
|
||||
// name we use for SMS OTP token in communicator
|
||||
val otpName = otp.name().trim()
|
||||
val defaultOtpName = resourceHelper.gs(R.string.smscommunicator_default_user_display_name)
|
||||
|
||||
// name we detect from OS
|
||||
val systemName = n1 ?: n2 ?: n3 ?: n4 ?: n5 ?: n6 ?: defaultOtpName
|
||||
val name = if (otpName.length > 0 && otpName != defaultOtpName) otpName else systemName
|
||||
return name
|
||||
}
|
||||
|
||||
private fun getCurrentDeviceModelString() =
|
||||
Build.MANUFACTURER + " " + Build.MODEL + " (" + Build.DEVICE + ")"
|
||||
|
||||
private fun prefsEncryptionIsDisabled() =
|
||||
buildHelper.isEngineeringMode() && !sp.getBoolean(resourceHelper.gs(R.string.key_maintenance_encrypt_exported_prefs), true)
|
||||
|
||||
private fun askForMasterPass(activity: Activity, @StringRes canceledMsg: Int, then: ((password: String) -> Unit)) {
|
||||
passwordCheck.queryPassword(activity, R.string.master_password, R.string.key_master_password, { password ->
|
||||
then(password)
|
||||
}, {
|
||||
ToastUtils.warnToast(activity, resourceHelper.gs(canceledMsg))
|
||||
})
|
||||
}
|
||||
|
||||
private fun askForMasterPassIfNeeded(activity: Activity, @StringRes canceledMsg: Int, then: ((password: String) -> Unit)) {
|
||||
if (prefsEncryptionIsDisabled()) {
|
||||
then("")
|
||||
} else {
|
||||
askForMasterPass(activity, canceledMsg, then)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assureMasterPasswordSet(activity: Activity, @StringRes wrongPwdTitle: Int): Boolean {
|
||||
if (!sp.contains(R.string.key_master_password) || (sp.getString(R.string.key_master_password, "") == "")) {
|
||||
WarningDialog.showWarning(activity,
|
||||
resourceHelper.gs(wrongPwdTitle),
|
||||
resourceHelper.gs(R.string.master_password_missing, resourceHelper.gs(R.string.configbuilder_general), resourceHelper.gs(R.string.protection)),
|
||||
R.string.nav_preferences, {
|
||||
val intent = Intent(activity, PreferencesActivity::class.java).apply {
|
||||
putExtra("id", R.xml.pref_general)
|
||||
}
|
||||
activity.startActivity(intent)
|
||||
})
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun askToConfirmExport(activity: Activity, then: ((password: String) -> Unit)) {
|
||||
if (!prefsEncryptionIsDisabled() && !assureMasterPasswordSet(activity, R.string.nav_export)) return
|
||||
|
||||
TwoMessagesAlertDialog.showAlert(activity, resourceHelper.gs(R.string.nav_export),
|
||||
resourceHelper.gs(R.string.export_to) + " " + encFile + " ?",
|
||||
resourceHelper.gs(R.string.password_preferences_encrypt_prompt), {
|
||||
askForMasterPassIfNeeded(activity, R.string.preferences_export_canceled, then)
|
||||
}, null, R.drawable.ic_header_export)
|
||||
}
|
||||
|
||||
private fun askToConfirmImport(activity: Activity, fileToImport: File, then: ((password: String) -> Unit)) {
|
||||
|
||||
if (encFile.exists()) {
|
||||
if (!assureMasterPasswordSet(activity, R.string.nav_import)) return
|
||||
|
||||
TwoMessagesAlertDialog.showAlert(activity, resourceHelper.gs(R.string.nav_import),
|
||||
resourceHelper.gs(R.string.import_from) + " " + fileToImport + " ?",
|
||||
resourceHelper.gs(R.string.password_preferences_decrypt_prompt), {
|
||||
askForMasterPass(activity, R.string.preferences_import_canceled, then)
|
||||
}, null, R.drawable.ic_header_import)
|
||||
|
||||
} else {
|
||||
OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.nav_import),
|
||||
resourceHelper.gs(R.string.import_from) + " " + fileToImport + " ?",
|
||||
Runnable { then("") })
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportSharedPreferences(activity: Activity) {
|
||||
askToConfirmExport(activity) { password ->
|
||||
try {
|
||||
val entries: MutableMap<String, String> = mutableMapOf()
|
||||
for ((key, value) in sp.getAll()) {
|
||||
entries[key] = value.toString()
|
||||
}
|
||||
|
||||
val prefs = Prefs(entries, prepareMetadata(activity))
|
||||
|
||||
classicPrefsFormat.savePreferences(file, prefs)
|
||||
encryptedPrefsFormat.savePreferences(encFile, prefs, password)
|
||||
|
||||
ToastUtils.okToast(activity, resourceHelper.gs(R.string.exported))
|
||||
} catch (e: FileNotFoundException) {
|
||||
ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + encFile)
|
||||
log.error(TAG, "Unhandled exception", e)
|
||||
} catch (e: IOException) {
|
||||
ToastUtils.errorToast(activity, e.message)
|
||||
log.error(TAG, "Unhandled exception", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun importSharedPreferences(fragment: Fragment) {
|
||||
fragment.activity?.let { importSharedPreferences(it) }
|
||||
}
|
||||
|
||||
fun importSharedPreferences(activity: Activity) {
|
||||
|
||||
val importFile = prefsImportFile()
|
||||
|
||||
askToConfirmImport(activity, importFile) { password ->
|
||||
|
||||
val format: PrefsFormat = if (encFile.exists()) encryptedPrefsFormat else classicPrefsFormat
|
||||
|
||||
try {
|
||||
|
||||
val prefs = format.loadPreferences(importFile, password)
|
||||
prefs.metadata = checkMetadata(prefs.metadata)
|
||||
|
||||
// import is OK when we do not have errors (warnings are allowed)
|
||||
val importOk = checkIfImportIsOk(prefs)
|
||||
|
||||
// if at end we allow to import preferences
|
||||
val importPossible = (importOk || buildHelper.isEngineeringMode()) && (prefs.values.size > 0)
|
||||
|
||||
PrefImportSummaryDialog.showSummary(activity, importOk, importPossible, prefs, {
|
||||
if (importPossible) {
|
||||
sp.clear()
|
||||
for ((key, value) in prefs.values) {
|
||||
if (value == "true" || value == "false") {
|
||||
sp.putBoolean(key, value.toBoolean())
|
||||
} else {
|
||||
sp.putString(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
restartAppAfterImport(activity)
|
||||
} else {
|
||||
// for impossible imports it should not be called
|
||||
ToastUtils.errorToast(activity, "Cannot import preferences!")
|
||||
}
|
||||
})
|
||||
|
||||
} catch (e: PrefFileNotFoundError) {
|
||||
ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + importFile)
|
||||
log.error(TAG, "Unhandled exception", e)
|
||||
} catch (e: PrefIOError) {
|
||||
log.error(TAG, "Unhandled exception", e)
|
||||
ToastUtils.errorToast(activity, e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check metadata for known issues, change their status and add info with explanations
|
||||
private 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()).getDays()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
private fun checkIfImportIsOk(prefs: Prefs): Boolean {
|
||||
var importOk = true
|
||||
|
||||
for ((_, value) in prefs.metadata) {
|
||||
if (value.status == PrefsStatus.ERROR)
|
||||
importOk = false;
|
||||
}
|
||||
return importOk
|
||||
}
|
||||
|
||||
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")
|
||||
rxBus.send(EventAppExit())
|
||||
if (context is Activity) {
|
||||
context.finish()
|
||||
}
|
||||
System.runFinalization()
|
||||
System.exit(0)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ class MaintenanceFragment : DaggerFragment() {
|
|||
@Inject lateinit var resourceHelper: ResourceHelper
|
||||
@Inject lateinit var treatmentsPlugin: TreatmentsPlugin
|
||||
@Inject lateinit var foodPlugin: FoodPlugin
|
||||
@Inject lateinit var importExportPrefs: ImportExportPrefs
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.maintenance_fragment, container, false)
|
||||
|
@ -45,13 +46,13 @@ class MaintenanceFragment : DaggerFragment() {
|
|||
}
|
||||
nav_export.setOnClickListener {
|
||||
// start activity for checking permissions...
|
||||
ImportExportPrefs.verifyStoragePermissions(this)
|
||||
ImportExportPrefs.exportSharedPreferences(this)
|
||||
importExportPrefs.verifyStoragePermissions(this)
|
||||
importExportPrefs.exportSharedPreferences(this)
|
||||
}
|
||||
nav_import.setOnClickListener {
|
||||
// start activity for checking permissions...
|
||||
ImportExportPrefs.verifyStoragePermissions(this)
|
||||
ImportExportPrefs.importSharedPreferences(this)
|
||||
importExportPrefs.verifyStoragePermissions(this)
|
||||
importExportPrefs.importSharedPreferences(this)
|
||||
}
|
||||
nav_logsettings.setOnClickListener { startActivity(Intent(activity, LogSettingActivity::class.java)) }
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreference
|
||||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.androidaps.BuildConfig
|
||||
import info.nightscout.androidaps.Config
|
||||
|
@ -16,6 +18,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus
|
|||
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||
import info.nightscout.androidaps.utils.textValidator.ValidatingEditTextPreference
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
import java.util.zip.ZipEntry
|
||||
|
@ -203,4 +206,13 @@ class MaintenancePlugin @Inject constructor(
|
|||
emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
return emailIntent
|
||||
}
|
||||
|
||||
override fun preprocessPreferences(preferenceFragment: PreferenceFragmentCompat) {
|
||||
super.preprocessPreferences(preferenceFragment)
|
||||
val encryptSwitch = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_maintenance_encrypt_exported_prefs)) as SwitchPreference?
|
||||
?: return
|
||||
encryptSwitch.isVisible = buildHelper.isEngineeringMode()
|
||||
encryptSwitch.isEnabled = buildHelper.isEngineeringMode()
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package info.nightscout.androidaps.plugins.general.maintenance.formats
|
||||
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import info.nightscout.androidaps.utils.storage.Storage
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ClassicPrefsFormat @Inject constructor(
|
||||
private var resourceHelper: ResourceHelper,
|
||||
private var storage: Storage
|
||||
) : PrefsFormat {
|
||||
|
||||
companion object {
|
||||
val FORMAT_KEY = "aaps_old"
|
||||
}
|
||||
|
||||
override fun savePreferences(file: File, prefs: Prefs, masterPassword: String?) {
|
||||
try {
|
||||
val contents = prefs.values.entries.joinToString("\n") { entry ->
|
||||
"${entry.key}::${entry.value}"
|
||||
}
|
||||
storage.putFileContents(file, contents)
|
||||
} catch (e: FileNotFoundException) {
|
||||
throw PrefFileNotFoundError(file.absolutePath)
|
||||
} catch (e: IOException) {
|
||||
throw PrefIOError(file.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadPreferences(file: File, masterPassword: String?): Prefs {
|
||||
var lineParts: Array<String>
|
||||
val entries: MutableMap<String, String> = mutableMapOf()
|
||||
val metadata: MutableMap<PrefsMetadataKey, PrefMetadata> = mutableMapOf()
|
||||
try {
|
||||
|
||||
val rawLines = storage.getFileContents(file).split("\n")
|
||||
rawLines.forEach { line ->
|
||||
lineParts = line.split("::").toTypedArray()
|
||||
if (lineParts.size == 2) {
|
||||
entries[lineParts[0]] = lineParts[1]
|
||||
}
|
||||
}
|
||||
|
||||
metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(FORMAT_KEY, PrefsStatus.WARN, resourceHelper.gs(R.string.metadata_warning_outdated_format))
|
||||
|
||||
return Prefs(entries, metadata)
|
||||
|
||||
} catch (e: FileNotFoundException) {
|
||||
throw PrefFileNotFoundError(file.absolutePath)
|
||||
} catch (e: IOException) {
|
||||
throw PrefIOError(file.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
package info.nightscout.androidaps.plugins.general.maintenance.formats
|
||||
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.utils.CryptoUtil
|
||||
import info.nightscout.androidaps.utils.extensions.hexStringToByteArray
|
||||
import info.nightscout.androidaps.utils.extensions.toHex
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import info.nightscout.androidaps.utils.storage.Storage
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class EncryptedPrefsFormat @Inject constructor(
|
||||
private var resourceHelper: ResourceHelper,
|
||||
private var storage: Storage
|
||||
) : PrefsFormat {
|
||||
|
||||
companion object {
|
||||
val FORMAT_KEY_ENC = "aaps_encrypted"
|
||||
val FORMAT_KEY_NOENC = "aaps_structured"
|
||||
|
||||
private val KEY_CONSCIENCE = "if you remove/change this, please make sure you know the consequences!"
|
||||
}
|
||||
|
||||
override fun savePreferences(file: File, prefs: Prefs, masterPassword: String?) {
|
||||
|
||||
val container = JSONObject()
|
||||
val content = JSONObject()
|
||||
val meta = JSONObject()
|
||||
|
||||
val encStatus = prefs.metadata[PrefsMetadataKey.ENCRYPTION]?.status ?: PrefsStatus.OK
|
||||
var encrypted = encStatus == PrefsStatus.OK && masterPassword != null
|
||||
|
||||
try {
|
||||
for ((key, value) in prefs.values.toSortedMap()) {
|
||||
content.put(key, value)
|
||||
}
|
||||
|
||||
for ((metaKey, metaEntry) in prefs.metadata) {
|
||||
if (metaKey == PrefsMetadataKey.FILE_FORMAT)
|
||||
continue
|
||||
if (metaKey == PrefsMetadataKey.ENCRYPTION)
|
||||
continue
|
||||
meta.put(metaKey.key, metaEntry.value)
|
||||
}
|
||||
|
||||
container.put(PrefsMetadataKey.FILE_FORMAT.key, if (encrypted) FORMAT_KEY_ENC else FORMAT_KEY_NOENC)
|
||||
container.put("metadata", meta)
|
||||
|
||||
val security = JSONObject()
|
||||
security.put("file_hash", "--to-be-calculated--")
|
||||
var encodedContent = ""
|
||||
|
||||
if (encrypted) {
|
||||
val salt = CryptoUtil.mineSalt()
|
||||
val rawContent = content.toString()
|
||||
val contentAttempt = CryptoUtil.encrypt(masterPassword!!, salt, rawContent)
|
||||
if (contentAttempt != null) {
|
||||
encodedContent = contentAttempt
|
||||
security.put("algorithm", "v1")
|
||||
security.put("salt", salt.toHex())
|
||||
security.put("content_hash", CryptoUtil.sha256(rawContent))
|
||||
} else {
|
||||
// fallback when encryption does not work
|
||||
encrypted = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!encrypted) {
|
||||
security.put("algorithm", "none")
|
||||
}
|
||||
|
||||
container.put("security", security)
|
||||
container.put("content", if (encrypted) encodedContent else content)
|
||||
|
||||
var fileContents = container.toString(2)
|
||||
val fileHash = CryptoUtil.hmac256(fileContents, KEY_CONSCIENCE)
|
||||
|
||||
fileContents = fileContents.replace(Regex("(\\\"file_hash\\\"\\s*\\:\\s*\\\")(--to-be-calculated--)(\\\")"), "$1" + fileHash + "$3")
|
||||
|
||||
storage.putFileContents(file, fileContents)
|
||||
|
||||
} catch (e: FileNotFoundException) {
|
||||
throw PrefFileNotFoundError(file.absolutePath)
|
||||
} catch (e: IOException) {
|
||||
throw PrefIOError(file.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadPreferences(file: File, masterPassword: String?): Prefs {
|
||||
|
||||
val entries: MutableMap<String, String> = mutableMapOf()
|
||||
val metadata: MutableMap<PrefsMetadataKey, PrefMetadata> = mutableMapOf()
|
||||
val issues = LinkedList<String>()
|
||||
try {
|
||||
|
||||
val jsonBody = storage.getFileContents(file)
|
||||
val fileContents = jsonBody.replace(Regex("(?is)(\\\"file_hash\\\"\\s*\\:\\s*\\\")([^\"]*)(\\\")"), "$1--to-be-calculated--$3")
|
||||
val calculatedFileHash = CryptoUtil.hmac256(fileContents, KEY_CONSCIENCE)
|
||||
val container = JSONObject(jsonBody)
|
||||
|
||||
if (container.has(PrefsMetadataKey.FILE_FORMAT.key) && container.has("security") && container.has("content") && container.has("metadata")) {
|
||||
val fileFormat = container.getString(PrefsMetadataKey.FILE_FORMAT.key)
|
||||
|
||||
if ((fileFormat != FORMAT_KEY_ENC) && (fileFormat != FORMAT_KEY_NOENC)) {
|
||||
throw PrefFormatError("Unsupported file format: " + fileFormat)
|
||||
}
|
||||
|
||||
val meta = container.getJSONObject("metadata")
|
||||
val security = container.getJSONObject("security")
|
||||
|
||||
metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(fileFormat, PrefsStatus.OK)
|
||||
for (key in meta.keys()) {
|
||||
val metaKey = PrefsMetadataKey.fromKey(key)
|
||||
if (metaKey != null) {
|
||||
metadata[metaKey] = PrefMetadata(meta.getString(key), PrefsStatus.OK)
|
||||
}
|
||||
}
|
||||
|
||||
val encrypted = fileFormat == FORMAT_KEY_ENC
|
||||
var secure: PrefsStatus = PrefsStatus.OK
|
||||
var decryptedOk = false
|
||||
var contentJsonObj: JSONObject? = null
|
||||
var insecurityReason = resourceHelper.gs(R.string.prefdecrypt_settings_tampered)
|
||||
|
||||
if (security.has("file_hash")) {
|
||||
if (calculatedFileHash != security.getString("file_hash")) {
|
||||
secure = PrefsStatus.ERROR
|
||||
issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_modified))
|
||||
}
|
||||
} else {
|
||||
secure = PrefsStatus.ERROR
|
||||
issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_missing_file_hash))
|
||||
}
|
||||
|
||||
if (encrypted) {
|
||||
if (security.has("algorithm") && security.get("algorithm") == "v1") {
|
||||
if (security.has("salt") && security.has("content_hash")) {
|
||||
|
||||
val salt = security.getString("salt").hexStringToByteArray()
|
||||
val decrypted = CryptoUtil.decrypt(masterPassword!!, salt, container.getString("content"))
|
||||
|
||||
if (decrypted != null) {
|
||||
try {
|
||||
val contentHash = CryptoUtil.sha256(decrypted)
|
||||
|
||||
if (contentHash == security.getString("content_hash")) {
|
||||
contentJsonObj = JSONObject(decrypted)
|
||||
decryptedOk = true
|
||||
} else {
|
||||
secure = PrefsStatus.ERROR
|
||||
issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_modified))
|
||||
}
|
||||
|
||||
} catch (e: JSONException) {
|
||||
secure = PrefsStatus.ERROR
|
||||
issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_parsing))
|
||||
}
|
||||
|
||||
} else {
|
||||
secure = PrefsStatus.ERROR
|
||||
issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_wrong_pass))
|
||||
insecurityReason = resourceHelper.gs(R.string.prefdecrypt_wrong_password)
|
||||
}
|
||||
|
||||
} else {
|
||||
secure = PrefsStatus.ERROR
|
||||
issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_wrong_format))
|
||||
}
|
||||
} else {
|
||||
secure = PrefsStatus.ERROR
|
||||
issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_wrong_algorithm))
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (secure == PrefsStatus.OK) {
|
||||
secure = PrefsStatus.WARN
|
||||
}
|
||||
|
||||
if (!(security.has("algorithm") && security.get("algorithm") == "none")) {
|
||||
secure = PrefsStatus.ERROR
|
||||
issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_wrong_algorithm))
|
||||
}
|
||||
|
||||
contentJsonObj = container.getJSONObject("content")
|
||||
decryptedOk = true
|
||||
}
|
||||
|
||||
if (decryptedOk && contentJsonObj != null) {
|
||||
for (key in contentJsonObj.keys()) {
|
||||
entries.put(key, contentJsonObj[key].toString())
|
||||
}
|
||||
}
|
||||
|
||||
val issuesStr: String? = if (issues.size > 0) issues.joinToString("\n") else null
|
||||
val encryptionDescStr = if (encrypted) {
|
||||
if (secure == PrefsStatus.OK) resourceHelper.gs(R.string.prefdecrypt_settings_secure) else insecurityReason
|
||||
} else {
|
||||
if (secure != PrefsStatus.ERROR) resourceHelper.gs(R.string.prefdecrypt_settings_unencrypted) else resourceHelper.gs(R.string.prefdecrypt_settings_tampered)
|
||||
}
|
||||
|
||||
metadata[PrefsMetadataKey.ENCRYPTION] = PrefMetadata(encryptionDescStr, secure, issuesStr)
|
||||
} else {
|
||||
metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(resourceHelper.gs(R.string.prefdecrypt_wrong_json), PrefsStatus.ERROR)
|
||||
}
|
||||
|
||||
return Prefs(entries, metadata)
|
||||
|
||||
} catch (e: FileNotFoundException) {
|
||||
throw PrefFileNotFoundError(file.absolutePath)
|
||||
} catch (e: IOException) {
|
||||
throw PrefIOError(file.absolutePath)
|
||||
} catch (e: JSONException) {
|
||||
throw PrefFormatError("Mallformed preferences JSON file: " + e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package info.nightscout.androidaps.plugins.general.maintenance.formats
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import info.nightscout.androidaps.R
|
||||
import java.io.File
|
||||
|
||||
enum class PrefsMetadataKey(val key: String, @DrawableRes val icon:Int, @StringRes val label:Int) {
|
||||
|
||||
FILE_FORMAT("format", R.drawable.ic_meta_format, R.string.metadata_label_format),
|
||||
CREATED_AT("created_at", R.drawable.ic_meta_date, R.string.metadata_label_created_at),
|
||||
AAPS_VERSION("aaps_version", R.drawable.ic_meta_version, R.string.metadata_label_aaps_version),
|
||||
AAPS_FLAVOUR("aaps_flavour", R.drawable.ic_meta_flavour, R.string.metadata_label_aaps_flavour),
|
||||
DEVICE_NAME("device_name", R.drawable.ic_meta_name, R.string.metadata_label_device_name),
|
||||
DEVICE_MODEL("device_model", R.drawable.ic_meta_model, R.string.metadata_label_device_model),
|
||||
ENCRYPTION("encryption", R.drawable.ic_meta_encryption, R.string.metadata_label_encryption);
|
||||
|
||||
companion object {
|
||||
private val keyToEnumMap = HashMap<String, PrefsMetadataKey>()
|
||||
|
||||
init {
|
||||
for (value in values()) {
|
||||
keyToEnumMap.put(value.key, value)
|
||||
}
|
||||
}
|
||||
|
||||
fun fromKey(key: String): PrefsMetadataKey? {
|
||||
if (keyToEnumMap.containsKey(key)) {
|
||||
return keyToEnumMap.get(key)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
fun formatForDisplay(context: Context, value:String): String {
|
||||
return when (this) {
|
||||
FILE_FORMAT -> when (value) {
|
||||
ClassicPrefsFormat.FORMAT_KEY -> context.getString(R.string.metadata_format_old)
|
||||
EncryptedPrefsFormat.FORMAT_KEY_ENC -> context.getString(R.string.metadata_format_new)
|
||||
EncryptedPrefsFormat.FORMAT_KEY_NOENC -> context.getString(R.string.metadata_format_debug)
|
||||
else -> context.getString(R.string.metadata_format_other)
|
||||
}
|
||||
CREATED_AT -> value.replace("T", " ").replace("Z", " (UTC)")
|
||||
else -> value
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class PrefMetadata(var value : String, var status : PrefsStatus, var info : String? = null)
|
||||
|
||||
data class Prefs(val values : Map<String, String>, var metadata : Map<PrefsMetadataKey, PrefMetadata>)
|
||||
|
||||
interface PrefsFormat {
|
||||
fun savePreferences(file: File, prefs: Prefs, masterPassword: String? = null)
|
||||
fun loadPreferences(file: File, masterPassword: String? = null) : Prefs
|
||||
}
|
||||
|
||||
enum class PrefsStatus(@DrawableRes val icon:Int) {
|
||||
OK(R.drawable.ic_meta_ok),
|
||||
WARN(R.drawable.ic_meta_warning),
|
||||
ERROR(R.drawable.ic_meta_error),
|
||||
UNKNOWN(R.drawable.ic_meta_error),
|
||||
DISABLED(R.drawable.ic_meta_error)
|
||||
}
|
||||
|
||||
class PrefFileNotFoundError(message: String) : Exception(message)
|
||||
class PrefIOError(message: String) : Exception(message)
|
||||
class PrefFormatError(message: String) : Exception(message)
|
|
@ -1463,7 +1463,7 @@ public class OverviewFragment extends DaggerFragment implements View.OnClickList
|
|||
|
||||
// add basal data
|
||||
if (pump.getPumpDescription().isTempBasalCapable && sp.getBoolean("showbasals", true)) {
|
||||
graphData.addBasals(fromTime, now, lowLine / graphData.maxY / 1.2d);
|
||||
graphData.addBasals(fromTime, now, lowLine / graphData.getMaxY() / 1.2d);
|
||||
}
|
||||
|
||||
// add target line
|
||||
|
|
|
@ -1,726 +0,0 @@
|
|||
package info.nightscout.androidaps.plugins.general.overview.graphData;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.DashPathEffect;
|
||||
import android.graphics.Paint;
|
||||
|
||||
import com.jjoe64.graphview.GraphView;
|
||||
import com.jjoe64.graphview.series.BarGraphSeries;
|
||||
import com.jjoe64.graphview.series.DataPoint;
|
||||
import com.jjoe64.graphview.series.LineGraphSeries;
|
||||
import com.jjoe64.graphview.series.Series;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.android.HasAndroidInjector;
|
||||
import info.nightscout.androidaps.Constants;
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.data.IobTotal;
|
||||
import info.nightscout.androidaps.data.Profile;
|
||||
import info.nightscout.androidaps.db.BgReading;
|
||||
import info.nightscout.androidaps.db.CareportalEvent;
|
||||
import info.nightscout.androidaps.db.ExtendedBolus;
|
||||
import info.nightscout.androidaps.db.ProfileSwitch;
|
||||
import info.nightscout.androidaps.db.TempTarget;
|
||||
import info.nightscout.androidaps.interfaces.ActivePluginProvider;
|
||||
import info.nightscout.androidaps.interfaces.TreatmentsInterface;
|
||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
||||
import info.nightscout.androidaps.logging.LTag;
|
||||
import info.nightscout.androidaps.plugins.aps.loop.APSResult;
|
||||
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
|
||||
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults;
|
||||
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction;
|
||||
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.AreaGraphSeries;
|
||||
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface;
|
||||
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DoubleDataPoint;
|
||||
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.FixedLineGraphSeries;
|
||||
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries;
|
||||
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.Scale;
|
||||
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint;
|
||||
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.TimeAsXAxisLabelFormatter;
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensData;
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult;
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.BasalData;
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin;
|
||||
import info.nightscout.androidaps.plugins.treatments.Treatment;
|
||||
import info.nightscout.androidaps.utils.DecimalFormatter;
|
||||
import info.nightscout.androidaps.utils.Round;
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
||||
|
||||
/**
|
||||
* Created by mike on 18.10.2017.
|
||||
*/
|
||||
|
||||
public class GraphData {
|
||||
|
||||
@Inject AAPSLogger aapsLogger;
|
||||
@Inject ProfileFunction profileFunction;
|
||||
@Inject ResourceHelper resourceHelper;
|
||||
@Inject ActivePluginProvider activePlugin;
|
||||
|
||||
private GraphView graph;
|
||||
public double maxY = Double.MIN_VALUE;
|
||||
private double minY = Double.MAX_VALUE;
|
||||
private List<BgReading> bgReadingsArray;
|
||||
private String units;
|
||||
private List<Series> series = new ArrayList<>();
|
||||
private TreatmentsInterface treatmentsPlugin;
|
||||
|
||||
|
||||
private IobCobCalculatorPlugin iobCobCalculatorPlugin; // Cannot be injected: HistoryBrowser
|
||||
|
||||
public GraphData(HasAndroidInjector injector, GraphView graph, IobCobCalculatorPlugin iobCobCalculatorPlugin) {
|
||||
injector.androidInjector().inject(this);
|
||||
units = profileFunction.getUnits();
|
||||
this.graph = graph;
|
||||
this.iobCobCalculatorPlugin = iobCobCalculatorPlugin;
|
||||
treatmentsPlugin = activePlugin.getActiveTreatments();
|
||||
}
|
||||
|
||||
public void addBgReadings(long fromTime, long toTime, double lowLine, double highLine, List<BgReading> predictions) {
|
||||
double maxBgValue = Double.MIN_VALUE;
|
||||
//bgReadingsArray = MainApp.getDbHelper().getBgreadingsDataFromTime(fromTime, true);
|
||||
bgReadingsArray = iobCobCalculatorPlugin.getBgReadings();
|
||||
List<DataPointWithLabelInterface> bgListArray = new ArrayList<>();
|
||||
|
||||
if (bgReadingsArray == null || bgReadingsArray.size() == 0) {
|
||||
aapsLogger.debug(LTag.OVERVIEW, "No BG data.");
|
||||
maxY = 10;
|
||||
minY = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
for (BgReading bg : bgReadingsArray) {
|
||||
if (bg.date < fromTime || bg.date > toTime) continue;
|
||||
if (bg.value > maxBgValue) maxBgValue = bg.value;
|
||||
bgListArray.add(bg);
|
||||
}
|
||||
if (predictions != null) {
|
||||
Collections.sort(predictions, (o1, o2) -> Double.compare(o1.getX(), o2.getX()));
|
||||
for (BgReading prediction : predictions) {
|
||||
if (prediction.value >= 40)
|
||||
bgListArray.add(prediction);
|
||||
}
|
||||
}
|
||||
|
||||
maxBgValue = Profile.fromMgdlToUnits(maxBgValue, units);
|
||||
maxBgValue = units.equals(Constants.MGDL) ? Round.roundTo(maxBgValue, 40d) + 80 : Round.roundTo(maxBgValue, 2d) + 4;
|
||||
if (highLine > maxBgValue) maxBgValue = highLine;
|
||||
int numOfVertLines = units.equals(Constants.MGDL) ? (int) (maxBgValue / 40 + 1) : (int) (maxBgValue / 2 + 1);
|
||||
|
||||
DataPointWithLabelInterface[] bg = new DataPointWithLabelInterface[bgListArray.size()];
|
||||
bg = bgListArray.toArray(bg);
|
||||
|
||||
|
||||
maxY = maxBgValue;
|
||||
minY = 0;
|
||||
// set manual y bounds to have nice steps
|
||||
graph.getGridLabelRenderer().setNumVerticalLabels(numOfVertLines);
|
||||
|
||||
addSeries(new PointsWithLabelGraphSeries<>(bg));
|
||||
}
|
||||
|
||||
public void addInRangeArea(long fromTime, long toTime, double lowLine, double highLine) {
|
||||
AreaGraphSeries<DoubleDataPoint> inRangeAreaSeries;
|
||||
|
||||
DoubleDataPoint[] inRangeAreaDataPoints = new DoubleDataPoint[]{
|
||||
new DoubleDataPoint(fromTime, lowLine, highLine),
|
||||
new DoubleDataPoint(toTime, lowLine, highLine)
|
||||
};
|
||||
inRangeAreaSeries = new AreaGraphSeries<>(inRangeAreaDataPoints);
|
||||
inRangeAreaSeries.setColor(0);
|
||||
inRangeAreaSeries.setDrawBackground(true);
|
||||
inRangeAreaSeries.setBackgroundColor(resourceHelper.gc(R.color.inrangebackground));
|
||||
|
||||
addSeries(inRangeAreaSeries);
|
||||
}
|
||||
|
||||
// scale in % of vertical size (like 0.3)
|
||||
public void addBasals(long fromTime, long toTime, double scale) {
|
||||
LineGraphSeries<ScaledDataPoint> basalsLineSeries;
|
||||
LineGraphSeries<ScaledDataPoint> absoluteBasalsLineSeries;
|
||||
LineGraphSeries<ScaledDataPoint> baseBasalsSeries;
|
||||
LineGraphSeries<ScaledDataPoint> tempBasalsSeries;
|
||||
|
||||
double maxBasalValueFound = 0d;
|
||||
Scale basalScale = new Scale();
|
||||
|
||||
List<ScaledDataPoint> baseBasalArray = new ArrayList<>();
|
||||
List<ScaledDataPoint> tempBasalArray = new ArrayList<>();
|
||||
List<ScaledDataPoint> basalLineArray = new ArrayList<>();
|
||||
List<ScaledDataPoint> absoluteBasalLineArray = new ArrayList<>();
|
||||
double lastLineBasal = 0;
|
||||
double lastAbsoluteLineBasal = -1;
|
||||
double lastBaseBasal = 0;
|
||||
double lastTempBasal = 0;
|
||||
for (long time = fromTime; time < toTime; time += 60 * 1000L) {
|
||||
Profile profile = profileFunction.getProfile(time);
|
||||
if (profile == null) continue;
|
||||
BasalData basalData = iobCobCalculatorPlugin.getBasalData(profile, time);
|
||||
double baseBasalValue = basalData.basal;
|
||||
double absoluteLineValue = baseBasalValue;
|
||||
double tempBasalValue = 0;
|
||||
double basal = 0d;
|
||||
if (basalData.isTempBasalRunning) {
|
||||
absoluteLineValue = tempBasalValue = basalData.tempBasalAbsolute;
|
||||
if (tempBasalValue != lastTempBasal) {
|
||||
tempBasalArray.add(new ScaledDataPoint(time, lastTempBasal, basalScale));
|
||||
tempBasalArray.add(new ScaledDataPoint(time, basal = tempBasalValue, basalScale));
|
||||
}
|
||||
if (lastBaseBasal != 0d) {
|
||||
baseBasalArray.add(new ScaledDataPoint(time, lastBaseBasal, basalScale));
|
||||
baseBasalArray.add(new ScaledDataPoint(time, 0d, basalScale));
|
||||
lastBaseBasal = 0d;
|
||||
}
|
||||
} else {
|
||||
if (baseBasalValue != lastBaseBasal) {
|
||||
baseBasalArray.add(new ScaledDataPoint(time, lastBaseBasal, basalScale));
|
||||
baseBasalArray.add(new ScaledDataPoint(time, basal = baseBasalValue, basalScale));
|
||||
lastBaseBasal = baseBasalValue;
|
||||
}
|
||||
if (lastTempBasal != 0) {
|
||||
tempBasalArray.add(new ScaledDataPoint(time, lastTempBasal, basalScale));
|
||||
tempBasalArray.add(new ScaledDataPoint(time, 0d, basalScale));
|
||||
}
|
||||
}
|
||||
|
||||
if (baseBasalValue != lastLineBasal) {
|
||||
basalLineArray.add(new ScaledDataPoint(time, lastLineBasal, basalScale));
|
||||
basalLineArray.add(new ScaledDataPoint(time, baseBasalValue, basalScale));
|
||||
}
|
||||
if (absoluteLineValue != lastAbsoluteLineBasal) {
|
||||
absoluteBasalLineArray.add(new ScaledDataPoint(time, lastAbsoluteLineBasal, basalScale));
|
||||
absoluteBasalLineArray.add(new ScaledDataPoint(time, basal, basalScale));
|
||||
}
|
||||
|
||||
lastAbsoluteLineBasal = absoluteLineValue;
|
||||
lastLineBasal = baseBasalValue;
|
||||
lastTempBasal = tempBasalValue;
|
||||
maxBasalValueFound = Math.max(maxBasalValueFound, Math.max(tempBasalValue, baseBasalValue));
|
||||
}
|
||||
|
||||
basalLineArray.add(new ScaledDataPoint(toTime, lastLineBasal, basalScale));
|
||||
baseBasalArray.add(new ScaledDataPoint(toTime, lastBaseBasal, basalScale));
|
||||
tempBasalArray.add(new ScaledDataPoint(toTime, lastTempBasal, basalScale));
|
||||
absoluteBasalLineArray.add(new ScaledDataPoint(toTime, lastAbsoluteLineBasal, basalScale));
|
||||
|
||||
ScaledDataPoint[] baseBasal = new ScaledDataPoint[baseBasalArray.size()];
|
||||
baseBasal = baseBasalArray.toArray(baseBasal);
|
||||
baseBasalsSeries = new LineGraphSeries<>(baseBasal);
|
||||
baseBasalsSeries.setDrawBackground(true);
|
||||
baseBasalsSeries.setBackgroundColor(resourceHelper.gc(R.color.basebasal));
|
||||
baseBasalsSeries.setThickness(0);
|
||||
|
||||
ScaledDataPoint[] tempBasal = new ScaledDataPoint[tempBasalArray.size()];
|
||||
tempBasal = tempBasalArray.toArray(tempBasal);
|
||||
tempBasalsSeries = new LineGraphSeries<>(tempBasal);
|
||||
tempBasalsSeries.setDrawBackground(true);
|
||||
tempBasalsSeries.setBackgroundColor(resourceHelper.gc(R.color.tempbasal));
|
||||
tempBasalsSeries.setThickness(0);
|
||||
|
||||
ScaledDataPoint[] basalLine = new ScaledDataPoint[basalLineArray.size()];
|
||||
basalLine = basalLineArray.toArray(basalLine);
|
||||
basalsLineSeries = new LineGraphSeries<>(basalLine);
|
||||
Paint paint = new Paint();
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(resourceHelper.getDisplayMetrics().scaledDensity * 2);
|
||||
paint.setPathEffect(new DashPathEffect(new float[]{2, 4}, 0));
|
||||
paint.setColor(resourceHelper.gc(R.color.basal));
|
||||
basalsLineSeries.setCustomPaint(paint);
|
||||
|
||||
ScaledDataPoint[] absoluteBasalLine = new ScaledDataPoint[absoluteBasalLineArray.size()];
|
||||
absoluteBasalLine = absoluteBasalLineArray.toArray(absoluteBasalLine);
|
||||
absoluteBasalsLineSeries = new LineGraphSeries<>(absoluteBasalLine);
|
||||
Paint absolutePaint = new Paint();
|
||||
absolutePaint.setStyle(Paint.Style.STROKE);
|
||||
absolutePaint.setStrokeWidth(resourceHelper.getDisplayMetrics().scaledDensity * 2);
|
||||
absolutePaint.setColor(resourceHelper.gc(R.color.basal));
|
||||
absoluteBasalsLineSeries.setCustomPaint(absolutePaint);
|
||||
|
||||
basalScale.setMultiplier(maxY * scale / maxBasalValueFound);
|
||||
|
||||
addSeries(baseBasalsSeries);
|
||||
addSeries(tempBasalsSeries);
|
||||
addSeries(basalsLineSeries);
|
||||
addSeries(absoluteBasalsLineSeries);
|
||||
}
|
||||
|
||||
public void addTargetLine(long fromTime, long toTime, Profile profile, LoopPlugin.LastRun lastRun) {
|
||||
LineGraphSeries<DataPoint> targetsSeries;
|
||||
|
||||
Scale targetsScale = new Scale();
|
||||
targetsScale.setMultiplier(1);
|
||||
|
||||
List<DataPoint> targetsSeriesArray = new ArrayList<>();
|
||||
double lastTarget = -1;
|
||||
|
||||
if (lastRun != null && lastRun.constraintsProcessed != null) {
|
||||
APSResult apsResult = lastRun.constraintsProcessed;
|
||||
long latestPredictionsTime = apsResult.getLatestPredictionsTime();
|
||||
if (latestPredictionsTime > toTime) {
|
||||
toTime = latestPredictionsTime;
|
||||
}
|
||||
}
|
||||
|
||||
for (long time = fromTime; time < toTime; time += 5 * 60 * 1000L) {
|
||||
TempTarget tt = treatmentsPlugin.getTempTargetFromHistory(time);
|
||||
double value;
|
||||
if (tt == null) {
|
||||
value = Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, profileFunction.getUnits());
|
||||
} else {
|
||||
value = Profile.fromMgdlToUnits(tt.target(), profileFunction.getUnits());
|
||||
}
|
||||
if (lastTarget != value) {
|
||||
if (lastTarget != -1)
|
||||
targetsSeriesArray.add(new DataPoint(time, lastTarget));
|
||||
targetsSeriesArray.add(new DataPoint(time, value));
|
||||
}
|
||||
lastTarget = value;
|
||||
}
|
||||
targetsSeriesArray.add(new DataPoint(toTime, lastTarget));
|
||||
|
||||
DataPoint[] targets = new DataPoint[targetsSeriesArray.size()];
|
||||
targets = targetsSeriesArray.toArray(targets);
|
||||
targetsSeries = new LineGraphSeries<>(targets);
|
||||
targetsSeries.setDrawBackground(false);
|
||||
targetsSeries.setColor(resourceHelper.gc(R.color.tempTargetBackground));
|
||||
targetsSeries.setThickness(2);
|
||||
|
||||
addSeries(targetsSeries);
|
||||
}
|
||||
|
||||
public void addTreatments(long fromTime, long endTime) {
|
||||
List<DataPointWithLabelInterface> filteredTreatments = new ArrayList<>();
|
||||
|
||||
List<Treatment> treatments = treatmentsPlugin.getTreatmentsFromHistory();
|
||||
|
||||
for (int tx = 0; tx < treatments.size(); tx++) {
|
||||
Treatment t = treatments.get(tx);
|
||||
if (t.getX() < fromTime || t.getX() > endTime) continue;
|
||||
if (t.isSMB && !t.isValid) continue;
|
||||
t.setY(getNearestBg((long) t.getX()));
|
||||
filteredTreatments.add(t);
|
||||
}
|
||||
|
||||
// ProfileSwitch
|
||||
List<ProfileSwitch> profileSwitches = treatmentsPlugin.getProfileSwitchesFromHistory().getList();
|
||||
|
||||
for (int tx = 0; tx < profileSwitches.size(); tx++) {
|
||||
DataPointWithLabelInterface t = profileSwitches.get(tx);
|
||||
if (t.getX() < fromTime || t.getX() > endTime) continue;
|
||||
filteredTreatments.add(t);
|
||||
}
|
||||
|
||||
// Extended bolus
|
||||
if (!activePlugin.getActivePump().isFakingTempsByExtendedBoluses()) {
|
||||
List<ExtendedBolus> extendedBoluses = treatmentsPlugin.getExtendedBolusesFromHistory().getList();
|
||||
|
||||
for (int tx = 0; tx < extendedBoluses.size(); tx++) {
|
||||
DataPointWithLabelInterface t = extendedBoluses.get(tx);
|
||||
if (t.getX() + t.getDuration() < fromTime || t.getX() > endTime) continue;
|
||||
if (t.getDuration() == 0) continue;
|
||||
t.setY(getNearestBg((long) t.getX()));
|
||||
filteredTreatments.add(t);
|
||||
}
|
||||
}
|
||||
|
||||
// Careportal
|
||||
List<CareportalEvent> careportalEvents = MainApp.getDbHelper().getCareportalEventsFromTime(fromTime - 6 * 60 * 60 * 1000, true);
|
||||
|
||||
for (int tx = 0; tx < careportalEvents.size(); tx++) {
|
||||
DataPointWithLabelInterface t = careportalEvents.get(tx);
|
||||
if (t.getX() + t.getDuration() < fromTime || t.getX() > endTime) continue;
|
||||
t.setY(getNearestBg((long) t.getX()));
|
||||
filteredTreatments.add(t);
|
||||
}
|
||||
|
||||
DataPointWithLabelInterface[] treatmentsArray = new DataPointWithLabelInterface[filteredTreatments.size()];
|
||||
treatmentsArray = filteredTreatments.toArray(treatmentsArray);
|
||||
addSeries(new PointsWithLabelGraphSeries<>(treatmentsArray));
|
||||
}
|
||||
|
||||
private double getNearestBg(long date) {
|
||||
if (bgReadingsArray == null)
|
||||
return Profile.fromMgdlToUnits(100, units);
|
||||
for (int r = 0; r < bgReadingsArray.size(); r++) {
|
||||
BgReading reading = bgReadingsArray.get(r);
|
||||
if (reading.date > date) continue;
|
||||
return Profile.fromMgdlToUnits(reading.value, units);
|
||||
}
|
||||
return bgReadingsArray.size() > 0
|
||||
? Profile.fromMgdlToUnits(bgReadingsArray.get(0).value, units) : Profile.fromMgdlToUnits(100, units);
|
||||
}
|
||||
|
||||
public void addActivity(long fromTime, long toTime, boolean useForScale, double scale) {
|
||||
FixedLineGraphSeries<ScaledDataPoint> actSeriesHist;
|
||||
List<ScaledDataPoint> actArrayHist = new ArrayList<>();
|
||||
FixedLineGraphSeries<ScaledDataPoint> actSeriesPred;
|
||||
List<ScaledDataPoint> actArrayPred = new ArrayList<>();
|
||||
|
||||
double now = System.currentTimeMillis();
|
||||
Scale actScale = new Scale();
|
||||
IobTotal total;
|
||||
double maxIAValue = 0;
|
||||
|
||||
for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) {
|
||||
Profile profile = profileFunction.getProfile(time);
|
||||
double act;
|
||||
if (profile == null) continue;
|
||||
total = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile);
|
||||
act = total.activity;
|
||||
|
||||
if (time <= now)
|
||||
actArrayHist.add(new ScaledDataPoint(time, act, actScale));
|
||||
else
|
||||
actArrayPred.add(new ScaledDataPoint(time, act, actScale));
|
||||
|
||||
maxIAValue = Math.max(maxIAValue, Math.abs(act));
|
||||
}
|
||||
|
||||
ScaledDataPoint[] actData = new ScaledDataPoint[actArrayHist.size()];
|
||||
actData = actArrayHist.toArray(actData);
|
||||
actSeriesHist = new FixedLineGraphSeries<>(actData);
|
||||
actSeriesHist.setDrawBackground(false);
|
||||
actSeriesHist.setColor(resourceHelper.gc(R.color.activity));
|
||||
actSeriesHist.setThickness(3);
|
||||
|
||||
addSeries(actSeriesHist);
|
||||
|
||||
actData = new ScaledDataPoint[actArrayPred.size()];
|
||||
actData = actArrayPred.toArray(actData);
|
||||
actSeriesPred = new FixedLineGraphSeries<>(actData);
|
||||
|
||||
Paint paint = new Paint();
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(3);
|
||||
paint.setPathEffect(new DashPathEffect(new float[]{4, 4}, 0));
|
||||
paint.setColor(resourceHelper.gc(R.color.activity));
|
||||
actSeriesPred.setCustomPaint(paint);
|
||||
|
||||
if (useForScale) {
|
||||
maxY = maxIAValue;
|
||||
minY = -maxIAValue;
|
||||
}
|
||||
actScale.setMultiplier(maxY * scale / maxIAValue);
|
||||
|
||||
addSeries(actSeriesPred);
|
||||
}
|
||||
|
||||
// scale in % of vertical size (like 0.3)
|
||||
public void addIob(long fromTime, long toTime, boolean useForScale, double scale, boolean showPrediction) {
|
||||
FixedLineGraphSeries<ScaledDataPoint> iobSeries;
|
||||
List<ScaledDataPoint> iobArray = new ArrayList<>();
|
||||
Double maxIobValueFound = Double.MIN_VALUE;
|
||||
double lastIob = 0;
|
||||
Scale iobScale = new Scale();
|
||||
|
||||
for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) {
|
||||
Profile profile = profileFunction.getProfile(time);
|
||||
double iob = 0d;
|
||||
if (profile != null)
|
||||
iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile).iob;
|
||||
if (Math.abs(lastIob - iob) > 0.02) {
|
||||
if (Math.abs(lastIob - iob) > 0.2)
|
||||
iobArray.add(new ScaledDataPoint(time, lastIob, iobScale));
|
||||
iobArray.add(new ScaledDataPoint(time, iob, iobScale));
|
||||
maxIobValueFound = Math.max(maxIobValueFound, Math.abs(iob));
|
||||
lastIob = iob;
|
||||
}
|
||||
}
|
||||
|
||||
ScaledDataPoint[] iobData = new ScaledDataPoint[iobArray.size()];
|
||||
iobData = iobArray.toArray(iobData);
|
||||
iobSeries = new FixedLineGraphSeries<>(iobData);
|
||||
iobSeries.setDrawBackground(true);
|
||||
iobSeries.setBackgroundColor(0x80FFFFFF & resourceHelper.gc(R.color.iob)); //50%
|
||||
iobSeries.setColor(resourceHelper.gc(R.color.iob));
|
||||
iobSeries.setThickness(3);
|
||||
|
||||
if (showPrediction) {
|
||||
AutosensResult lastAutosensResult;
|
||||
AutosensData autosensData = iobCobCalculatorPlugin.getLastAutosensDataSynchronized("GraphData");
|
||||
if (autosensData == null)
|
||||
lastAutosensResult = new AutosensResult();
|
||||
else
|
||||
lastAutosensResult = autosensData.autosensResult;
|
||||
boolean isTempTarget = treatmentsPlugin.getTempTargetFromHistory(System.currentTimeMillis()) != null;
|
||||
|
||||
List<DataPointWithLabelInterface> iobPred = new ArrayList<>();
|
||||
IobTotal[] iobPredArray = iobCobCalculatorPlugin.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget);
|
||||
for (IobTotal i : iobPredArray) {
|
||||
iobPred.add(i.setColor(resourceHelper.gc(R.color.iobPredAS)));
|
||||
maxIobValueFound = Math.max(maxIobValueFound, Math.abs(i.iob));
|
||||
}
|
||||
DataPointWithLabelInterface[] iobp = new DataPointWithLabelInterface[iobPred.size()];
|
||||
iobp = iobPred.toArray(iobp);
|
||||
addSeries(new PointsWithLabelGraphSeries<>(iobp));
|
||||
|
||||
|
||||
List<DataPointWithLabelInterface> iobPred2 = new ArrayList<>();
|
||||
IobTotal[] iobPredArray2 = iobCobCalculatorPlugin.calculateIobArrayForSMB(new AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget);
|
||||
for (IobTotal i : iobPredArray2) {
|
||||
iobPred2.add(i.setColor(resourceHelper.gc(R.color.iobPred)));
|
||||
maxIobValueFound = Math.max(maxIobValueFound, Math.abs(i.iob));
|
||||
}
|
||||
DataPointWithLabelInterface[] iobp2 = new DataPointWithLabelInterface[iobPred2.size()];
|
||||
iobp2 = iobPred2.toArray(iobp2);
|
||||
addSeries(new PointsWithLabelGraphSeries<>(iobp2));
|
||||
|
||||
aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray));
|
||||
aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(1) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray2));
|
||||
}
|
||||
|
||||
if (useForScale) {
|
||||
maxY = maxIobValueFound;
|
||||
minY = -maxIobValueFound;
|
||||
}
|
||||
|
||||
iobScale.setMultiplier(maxY * scale / maxIobValueFound);
|
||||
|
||||
addSeries(iobSeries);
|
||||
}
|
||||
|
||||
// scale in % of vertical size (like 0.3)
|
||||
public void addCob(long fromTime, long toTime, boolean useForScale, double scale) {
|
||||
List<DataPointWithLabelInterface> minFailoverActiveList = new ArrayList<>();
|
||||
FixedLineGraphSeries<ScaledDataPoint> cobSeries;
|
||||
List<ScaledDataPoint> cobArray = new ArrayList<>();
|
||||
Double maxCobValueFound = 0d;
|
||||
int lastCob = 0;
|
||||
Scale cobScale = new Scale();
|
||||
|
||||
for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) {
|
||||
AutosensData autosensData = iobCobCalculatorPlugin.getAutosensData(time);
|
||||
if (autosensData != null) {
|
||||
int cob = (int) autosensData.cob;
|
||||
if (cob != lastCob) {
|
||||
if (autosensData.carbsFromBolus > 0)
|
||||
cobArray.add(new ScaledDataPoint(time, lastCob, cobScale));
|
||||
cobArray.add(new ScaledDataPoint(time, cob, cobScale));
|
||||
maxCobValueFound = Math.max(maxCobValueFound, cob);
|
||||
lastCob = cob;
|
||||
}
|
||||
if (autosensData.failoverToMinAbsorbtionRate) {
|
||||
autosensData.setScale(cobScale);
|
||||
autosensData.setChartTime(time);
|
||||
minFailoverActiveList.add(autosensData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// COB
|
||||
ScaledDataPoint[] cobData = new ScaledDataPoint[cobArray.size()];
|
||||
cobData = cobArray.toArray(cobData);
|
||||
cobSeries = new FixedLineGraphSeries<>(cobData);
|
||||
cobSeries.setDrawBackground(true);
|
||||
cobSeries.setBackgroundColor(0x80FFFFFF & resourceHelper.gc(R.color.cob)); //50%
|
||||
cobSeries.setColor(resourceHelper.gc(R.color.cob));
|
||||
cobSeries.setThickness(3);
|
||||
|
||||
if (useForScale) {
|
||||
maxY = maxCobValueFound;
|
||||
minY = 0;
|
||||
}
|
||||
|
||||
cobScale.setMultiplier(maxY * scale / maxCobValueFound);
|
||||
|
||||
addSeries(cobSeries);
|
||||
|
||||
DataPointWithLabelInterface[] minFailover = new DataPointWithLabelInterface[minFailoverActiveList.size()];
|
||||
minFailover = minFailoverActiveList.toArray(minFailover);
|
||||
addSeries(new PointsWithLabelGraphSeries<>(minFailover));
|
||||
}
|
||||
|
||||
// scale in % of vertical size (like 0.3)
|
||||
public void addDeviations(long fromTime, long toTime, boolean useForScale, double scale) {
|
||||
class DeviationDataPoint extends ScaledDataPoint {
|
||||
public int color;
|
||||
|
||||
private DeviationDataPoint(double x, double y, int color, Scale scale) {
|
||||
super(x, y, scale);
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
BarGraphSeries<DeviationDataPoint> devSeries;
|
||||
List<DeviationDataPoint> devArray = new ArrayList<>();
|
||||
Double maxDevValueFound = 0d;
|
||||
Scale devScale = new Scale();
|
||||
|
||||
for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) {
|
||||
AutosensData autosensData = iobCobCalculatorPlugin.getAutosensData(time);
|
||||
if (autosensData != null) {
|
||||
int color = resourceHelper.gc(R.color.deviationblack); // "="
|
||||
if (autosensData.type.equals("") || autosensData.type.equals("non-meal")) {
|
||||
if (autosensData.pastSensitivity.equals("C"))
|
||||
color = resourceHelper.gc(R.color.deviationgrey);
|
||||
if (autosensData.pastSensitivity.equals("+"))
|
||||
color = resourceHelper.gc(R.color.deviationgreen);
|
||||
if (autosensData.pastSensitivity.equals("-"))
|
||||
color = resourceHelper.gc(R.color.deviationred);
|
||||
} else if (autosensData.type.equals("uam")) {
|
||||
color = resourceHelper.gc(R.color.uam);
|
||||
} else if (autosensData.type.equals("csf")) {
|
||||
color = resourceHelper.gc(R.color.deviationgrey);
|
||||
}
|
||||
devArray.add(new DeviationDataPoint(time, autosensData.deviation, color, devScale));
|
||||
maxDevValueFound = Math.max(maxDevValueFound, Math.abs(autosensData.deviation));
|
||||
}
|
||||
}
|
||||
|
||||
// DEVIATIONS
|
||||
DeviationDataPoint[] devData = new DeviationDataPoint[devArray.size()];
|
||||
devData = devArray.toArray(devData);
|
||||
devSeries = new BarGraphSeries<>(devData);
|
||||
devSeries.setValueDependentColor(data -> data.color);
|
||||
|
||||
if (useForScale) {
|
||||
maxY = maxDevValueFound;
|
||||
minY = -maxY;
|
||||
}
|
||||
|
||||
devScale.setMultiplier(maxY * scale / maxDevValueFound);
|
||||
|
||||
addSeries(devSeries);
|
||||
}
|
||||
|
||||
// scale in % of vertical size (like 0.3)
|
||||
public void addRatio(long fromTime, long toTime, boolean useForScale, double scale) {
|
||||
LineGraphSeries<ScaledDataPoint> ratioSeries;
|
||||
List<ScaledDataPoint> ratioArray = new ArrayList<>();
|
||||
double maxRatioValueFound = Double.MIN_VALUE;
|
||||
double minRatioValueFound = Double.MAX_VALUE;
|
||||
Scale ratioScale = new Scale();
|
||||
|
||||
for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) {
|
||||
AutosensData autosensData = iobCobCalculatorPlugin.getAutosensData(time);
|
||||
if (autosensData != null) {
|
||||
ratioArray.add(new ScaledDataPoint(time, autosensData.autosensResult.ratio - 1, ratioScale));
|
||||
maxRatioValueFound = Math.max(maxRatioValueFound, autosensData.autosensResult.ratio - 1);
|
||||
minRatioValueFound = Math.min(minRatioValueFound, autosensData.autosensResult.ratio - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// RATIOS
|
||||
ScaledDataPoint[] ratioData = new ScaledDataPoint[ratioArray.size()];
|
||||
ratioData = ratioArray.toArray(ratioData);
|
||||
ratioSeries = new LineGraphSeries<>(ratioData);
|
||||
ratioSeries.setColor(resourceHelper.gc(R.color.ratio));
|
||||
ratioSeries.setThickness(3);
|
||||
|
||||
if (useForScale) {
|
||||
maxY = Math.max(maxRatioValueFound, Math.abs(minRatioValueFound));
|
||||
minY = -maxY;
|
||||
}
|
||||
|
||||
ratioScale.setMultiplier(maxY * scale / Math.max(maxRatioValueFound, Math.abs(minRatioValueFound)));
|
||||
|
||||
addSeries(ratioSeries);
|
||||
}
|
||||
|
||||
// scale in % of vertical size (like 0.3)
|
||||
public void addDeviationSlope(long fromTime, long toTime, boolean useForScale, double scale) {
|
||||
LineGraphSeries<ScaledDataPoint> dsMaxSeries;
|
||||
LineGraphSeries<ScaledDataPoint> dsMinSeries;
|
||||
List<ScaledDataPoint> dsMaxArray = new ArrayList<>();
|
||||
List<ScaledDataPoint> dsMinArray = new ArrayList<>();
|
||||
double maxFromMaxValueFound = 0d;
|
||||
double maxFromMinValueFound = 0d;
|
||||
Scale dsMaxScale = new Scale();
|
||||
Scale dsMinScale = new Scale();
|
||||
|
||||
for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) {
|
||||
AutosensData autosensData = iobCobCalculatorPlugin.getAutosensData(time);
|
||||
if (autosensData != null) {
|
||||
dsMaxArray.add(new ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale));
|
||||
dsMinArray.add(new ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale));
|
||||
maxFromMaxValueFound = Math.max(maxFromMaxValueFound, Math.abs(autosensData.slopeFromMaxDeviation));
|
||||
maxFromMinValueFound = Math.max(maxFromMinValueFound, Math.abs(autosensData.slopeFromMinDeviation));
|
||||
}
|
||||
}
|
||||
|
||||
// Slopes
|
||||
ScaledDataPoint[] ratioMaxData = new ScaledDataPoint[dsMaxArray.size()];
|
||||
ratioMaxData = dsMaxArray.toArray(ratioMaxData);
|
||||
dsMaxSeries = new LineGraphSeries<>(ratioMaxData);
|
||||
dsMaxSeries.setColor(resourceHelper.gc(R.color.devslopepos));
|
||||
dsMaxSeries.setThickness(3);
|
||||
|
||||
ScaledDataPoint[] ratioMinData = new ScaledDataPoint[dsMinArray.size()];
|
||||
ratioMinData = dsMinArray.toArray(ratioMinData);
|
||||
dsMinSeries = new LineGraphSeries<>(ratioMinData);
|
||||
dsMinSeries.setColor(resourceHelper.gc(R.color.devslopeneg));
|
||||
dsMinSeries.setThickness(3);
|
||||
|
||||
if (useForScale) {
|
||||
maxY = Math.max(maxFromMaxValueFound, maxFromMinValueFound);
|
||||
minY = -maxY;
|
||||
}
|
||||
|
||||
dsMaxScale.setMultiplier(maxY * scale / maxFromMaxValueFound);
|
||||
dsMinScale.setMultiplier(maxY * scale / maxFromMinValueFound);
|
||||
|
||||
addSeries(dsMaxSeries);
|
||||
addSeries(dsMinSeries);
|
||||
}
|
||||
|
||||
// scale in % of vertical size (like 0.3)
|
||||
public void addNowLine(long now) {
|
||||
LineGraphSeries<DataPoint> seriesNow;
|
||||
DataPoint[] nowPoints = new DataPoint[]{
|
||||
new DataPoint(now, 0),
|
||||
new DataPoint(now, maxY)
|
||||
};
|
||||
|
||||
seriesNow = new LineGraphSeries<>(nowPoints);
|
||||
seriesNow.setDrawDataPoints(false);
|
||||
// custom paint to make a dotted line
|
||||
Paint paint = new Paint();
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(2);
|
||||
paint.setPathEffect(new DashPathEffect(new float[]{10, 20}, 0));
|
||||
paint.setColor(Color.WHITE);
|
||||
seriesNow.setCustomPaint(paint);
|
||||
|
||||
addSeries(seriesNow);
|
||||
}
|
||||
|
||||
public void formatAxis(long fromTime, long endTime) {
|
||||
graph.getViewport().setMaxX(endTime);
|
||||
graph.getViewport().setMinX(fromTime);
|
||||
graph.getViewport().setXAxisBoundsManual(true);
|
||||
graph.getGridLabelRenderer().setLabelFormatter(new TimeAsXAxisLabelFormatter("HH"));
|
||||
graph.getGridLabelRenderer().setNumHorizontalLabels(7); // only 7 because of the space
|
||||
}
|
||||
|
||||
private void addSeries(Series s) {
|
||||
series.add(s);
|
||||
}
|
||||
|
||||
public void performUpdate() {
|
||||
// clear old data
|
||||
graph.getSeries().clear();
|
||||
|
||||
// add precalculated series
|
||||
for (Series s : series) {
|
||||
if (!s.isEmpty()) {
|
||||
s.onGraphViewAttached(graph);
|
||||
graph.getSeries().add(s);
|
||||
}
|
||||
}
|
||||
|
||||
double step = 1d;
|
||||
if (maxY < 1) step = 0.1d;
|
||||
graph.getViewport().setMaxY(Round.ceilTo(maxY, step));
|
||||
graph.getViewport().setMinY(Round.floorTo(minY, step));
|
||||
graph.getViewport().setYAxisBoundsManual(true);
|
||||
|
||||
// draw it
|
||||
graph.onDataChanged(false, false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,569 @@
|
|||
package info.nightscout.androidaps.plugins.general.overview.graphData
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.DashPathEffect
|
||||
import android.graphics.Paint
|
||||
import com.jjoe64.graphview.GraphView
|
||||
import com.jjoe64.graphview.series.BarGraphSeries
|
||||
import com.jjoe64.graphview.series.DataPoint
|
||||
import com.jjoe64.graphview.series.LineGraphSeries
|
||||
import com.jjoe64.graphview.series.Series
|
||||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.androidaps.Constants
|
||||
import info.nightscout.androidaps.MainApp
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.data.IobTotal
|
||||
import info.nightscout.androidaps.data.Profile
|
||||
import info.nightscout.androidaps.db.BgReading
|
||||
import info.nightscout.androidaps.interfaces.ActivePluginProvider
|
||||
import info.nightscout.androidaps.interfaces.TreatmentsInterface
|
||||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
import info.nightscout.androidaps.logging.LTag
|
||||
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin.LastRun
|
||||
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
|
||||
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction
|
||||
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.*
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
|
||||
import info.nightscout.androidaps.utils.DecimalFormatter
|
||||
import info.nightscout.androidaps.utils.Round
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class GraphData(injector: HasAndroidInjector, private val graph: GraphView, private val iobCobCalculatorPlugin: IobCobCalculatorPlugin) {
|
||||
|
||||
// IobCobCalculatorPlugin Cannot be injected: HistoryBrowser
|
||||
@Inject lateinit var aapsLogger: AAPSLogger
|
||||
@Inject lateinit var profileFunction: ProfileFunction
|
||||
@Inject lateinit var resourceHelper: ResourceHelper
|
||||
@Inject lateinit var activePlugin: ActivePluginProvider
|
||||
|
||||
private val treatmentsPlugin: TreatmentsInterface
|
||||
|
||||
var maxY = Double.MIN_VALUE
|
||||
private var minY = Double.MAX_VALUE
|
||||
private var bgReadingsArray: List<BgReading>? = null
|
||||
private val units: String
|
||||
private val series: MutableList<Series<*>> = ArrayList()
|
||||
|
||||
init {
|
||||
injector.androidInjector().inject(this)
|
||||
units = profileFunction.getUnits()
|
||||
treatmentsPlugin = activePlugin.activeTreatments
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun addBgReadings(fromTime: Long, toTime: Long, lowLine: Double, highLine: Double, predictions: MutableList<BgReading>?) {
|
||||
var maxBgValue = Double.MIN_VALUE
|
||||
bgReadingsArray = iobCobCalculatorPlugin.bgReadings
|
||||
if (bgReadingsArray?.isEmpty() != false) {
|
||||
aapsLogger.debug(LTag.OVERVIEW, "No BG data.")
|
||||
maxY = 10.0
|
||||
minY = 0.0
|
||||
return
|
||||
}
|
||||
val bgListArray: MutableList<DataPointWithLabelInterface> = ArrayList()
|
||||
for (bg in bgReadingsArray!!) {
|
||||
if (bg.date < fromTime || bg.date > toTime) continue
|
||||
if (bg.value > maxBgValue) maxBgValue = bg.value
|
||||
bgListArray.add(bg)
|
||||
}
|
||||
if (predictions != null) {
|
||||
predictions.sortWith(Comparator { o1: BgReading, o2: BgReading -> o1.x.compareTo(o2.x) })
|
||||
for (prediction in predictions) if (prediction.value >= 40) bgListArray.add(prediction)
|
||||
}
|
||||
maxBgValue = Profile.fromMgdlToUnits(maxBgValue, units)
|
||||
maxBgValue = if (units == Constants.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4
|
||||
if (highLine > maxBgValue) maxBgValue = highLine
|
||||
val numOfVerticalLines = if (units == Constants.MGDL) (maxBgValue / 40 + 1).toInt() else (maxBgValue / 2 + 1).toInt()
|
||||
maxY = maxBgValue
|
||||
minY = 0.0
|
||||
// set manual y bounds to have nice steps
|
||||
graph.gridLabelRenderer.numVerticalLabels = numOfVerticalLines
|
||||
addSeries(PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }))
|
||||
}
|
||||
|
||||
fun addInRangeArea(fromTime: Long, toTime: Long, lowLine: Double, highLine: Double) {
|
||||
val inRangeAreaSeries: AreaGraphSeries<DoubleDataPoint>
|
||||
val inRangeAreaDataPoints = arrayOf(
|
||||
DoubleDataPoint(fromTime.toDouble(), lowLine, highLine),
|
||||
DoubleDataPoint(toTime.toDouble(), lowLine, highLine)
|
||||
)
|
||||
inRangeAreaSeries = AreaGraphSeries(inRangeAreaDataPoints)
|
||||
inRangeAreaSeries.color = 0
|
||||
inRangeAreaSeries.isDrawBackground = true
|
||||
inRangeAreaSeries.backgroundColor = resourceHelper.gc(R.color.inrangebackground)
|
||||
addSeries(inRangeAreaSeries)
|
||||
}
|
||||
|
||||
// scale in % of vertical size (like 0.3)
|
||||
fun addBasals(fromTime: Long, toTime: Long, scale: Double) {
|
||||
var maxBasalValueFound = 0.0
|
||||
val basalScale = Scale()
|
||||
val baseBasalArray: MutableList<ScaledDataPoint> = ArrayList()
|
||||
val tempBasalArray: MutableList<ScaledDataPoint> = ArrayList()
|
||||
val basalLineArray: MutableList<ScaledDataPoint> = ArrayList()
|
||||
val absoluteBasalLineArray: MutableList<ScaledDataPoint> = ArrayList()
|
||||
var lastLineBasal = 0.0
|
||||
var lastAbsoluteLineBasal = -1.0
|
||||
var lastBaseBasal = 0.0
|
||||
var lastTempBasal = 0.0
|
||||
var time = fromTime
|
||||
while (time < toTime) {
|
||||
val profile = profileFunction.getProfile(time)
|
||||
if (profile == null) {
|
||||
time += 60 * 1000L
|
||||
continue
|
||||
}
|
||||
val basalData = iobCobCalculatorPlugin.getBasalData(profile, time)
|
||||
val baseBasalValue = basalData.basal
|
||||
var absoluteLineValue = baseBasalValue
|
||||
var tempBasalValue = 0.0
|
||||
var basal = 0.0
|
||||
if (basalData.isTempBasalRunning) {
|
||||
tempBasalValue = basalData.tempBasalAbsolute
|
||||
absoluteLineValue = tempBasalValue
|
||||
if (tempBasalValue != lastTempBasal) {
|
||||
tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale))
|
||||
tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, basalScale))
|
||||
}
|
||||
if (lastBaseBasal != 0.0) {
|
||||
baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale))
|
||||
baseBasalArray.add(ScaledDataPoint(time, 0.0, basalScale))
|
||||
lastBaseBasal = 0.0
|
||||
}
|
||||
} else {
|
||||
if (baseBasalValue != lastBaseBasal) {
|
||||
baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale))
|
||||
baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, basalScale))
|
||||
lastBaseBasal = baseBasalValue
|
||||
}
|
||||
if (lastTempBasal != 0.0) {
|
||||
tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale))
|
||||
tempBasalArray.add(ScaledDataPoint(time, 0.0, basalScale))
|
||||
}
|
||||
}
|
||||
if (baseBasalValue != lastLineBasal) {
|
||||
basalLineArray.add(ScaledDataPoint(time, lastLineBasal, basalScale))
|
||||
basalLineArray.add(ScaledDataPoint(time, baseBasalValue, basalScale))
|
||||
}
|
||||
if (absoluteLineValue != lastAbsoluteLineBasal) {
|
||||
absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, basalScale))
|
||||
absoluteBasalLineArray.add(ScaledDataPoint(time, basal, basalScale))
|
||||
}
|
||||
lastAbsoluteLineBasal = absoluteLineValue
|
||||
lastLineBasal = baseBasalValue
|
||||
lastTempBasal = tempBasalValue
|
||||
maxBasalValueFound = max(maxBasalValueFound, max(tempBasalValue, baseBasalValue))
|
||||
time += 60 * 1000L
|
||||
}
|
||||
|
||||
// final points
|
||||
basalLineArray.add(ScaledDataPoint(toTime, lastLineBasal, basalScale))
|
||||
baseBasalArray.add(ScaledDataPoint(toTime, lastBaseBasal, basalScale))
|
||||
tempBasalArray.add(ScaledDataPoint(toTime, lastTempBasal, basalScale))
|
||||
absoluteBasalLineArray.add(ScaledDataPoint(toTime, lastAbsoluteLineBasal, basalScale))
|
||||
|
||||
// create series
|
||||
addSeries(LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also {
|
||||
it.isDrawBackground = true
|
||||
it.backgroundColor = resourceHelper.gc(R.color.basebasal)
|
||||
it.thickness = 0
|
||||
})
|
||||
addSeries(LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also {
|
||||
it.isDrawBackground = true
|
||||
it.backgroundColor = resourceHelper.gc(R.color.tempbasal)
|
||||
it.thickness = 0
|
||||
})
|
||||
addSeries(LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also {
|
||||
it.setCustomPaint(Paint().also { paint ->
|
||||
paint.style = Paint.Style.STROKE
|
||||
paint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2
|
||||
paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f)
|
||||
paint.color = resourceHelper.gc(R.color.basal)
|
||||
})
|
||||
})
|
||||
addSeries(LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also {
|
||||
it.setCustomPaint(Paint().also { absolutePaint ->
|
||||
absolutePaint.style = Paint.Style.STROKE
|
||||
absolutePaint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2
|
||||
absolutePaint.color = resourceHelper.gc(R.color.basal)
|
||||
})
|
||||
})
|
||||
basalScale.setMultiplier(maxY * scale / maxBasalValueFound)
|
||||
}
|
||||
|
||||
fun addTargetLine(fromTime: Long, toTimeParam: Long, profile: Profile, lastRun: LastRun?) {
|
||||
var toTime = toTimeParam
|
||||
val targetsSeriesArray: MutableList<DataPoint> = ArrayList()
|
||||
var lastTarget = -1.0
|
||||
lastRun?.constraintsProcessed?.let { toTime = max(it.latestPredictionsTime, toTime) }
|
||||
var time = fromTime
|
||||
while (time < toTime) {
|
||||
val tt = treatmentsPlugin.getTempTargetFromHistory(time)
|
||||
var value: Double
|
||||
value = if (tt == null) {
|
||||
Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, units)
|
||||
} else {
|
||||
Profile.fromMgdlToUnits(tt.target(), units)
|
||||
}
|
||||
if (lastTarget != value) {
|
||||
if (lastTarget != -1.0) targetsSeriesArray.add(DataPoint(time.toDouble(), lastTarget))
|
||||
targetsSeriesArray.add(DataPoint(time.toDouble(), value))
|
||||
}
|
||||
lastTarget = value
|
||||
time += 5 * 60 * 1000L
|
||||
}
|
||||
// final point
|
||||
targetsSeriesArray.add(DataPoint(toTime.toDouble(), lastTarget))
|
||||
// create series
|
||||
addSeries(LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also {
|
||||
it.isDrawBackground = false
|
||||
it.color = resourceHelper.gc(R.color.tempTargetBackground)
|
||||
it.thickness = 2
|
||||
})
|
||||
}
|
||||
|
||||
fun addTreatments(fromTime: Long, endTime: Long) {
|
||||
val filteredTreatments: MutableList<DataPointWithLabelInterface> = ArrayList()
|
||||
val treatments = treatmentsPlugin.treatmentsFromHistory
|
||||
for (tx in treatments.indices) {
|
||||
val t = treatments[tx]
|
||||
if (t.x < fromTime || t.x > endTime) continue
|
||||
if (t.isSMB && !t.isValid) continue
|
||||
t.y = getNearestBg(t.x.toLong())
|
||||
filteredTreatments.add(t)
|
||||
}
|
||||
|
||||
// ProfileSwitch
|
||||
val profileSwitches = treatmentsPlugin.profileSwitchesFromHistory.list
|
||||
for (tx in profileSwitches.indices) {
|
||||
val t: DataPointWithLabelInterface = profileSwitches[tx]
|
||||
if (t.x < fromTime || t.x > endTime) continue
|
||||
filteredTreatments.add(t)
|
||||
}
|
||||
|
||||
// Extended bolus
|
||||
if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) {
|
||||
val extendedBoluses = treatmentsPlugin.extendedBolusesFromHistory.list
|
||||
for (tx in extendedBoluses.indices) {
|
||||
val t: DataPointWithLabelInterface = extendedBoluses[tx]
|
||||
if (t.x + t.duration < fromTime || t.x > endTime) continue
|
||||
if (t.duration == 0L) continue
|
||||
t.y = getNearestBg(t.x.toLong())
|
||||
filteredTreatments.add(t)
|
||||
}
|
||||
}
|
||||
|
||||
// Careportal
|
||||
val careportalEvents = MainApp.getDbHelper().getCareportalEventsFromTime(fromTime - 6 * 60 * 60 * 1000, true)
|
||||
for (tx in careportalEvents.indices) {
|
||||
val t: DataPointWithLabelInterface = careportalEvents[tx]
|
||||
if (t.x + t.duration < fromTime || t.x > endTime) continue
|
||||
t.y = getNearestBg(t.x.toLong())
|
||||
filteredTreatments.add(t)
|
||||
}
|
||||
addSeries(PointsWithLabelGraphSeries(Array(filteredTreatments.size) { i -> filteredTreatments[i] }))
|
||||
}
|
||||
|
||||
private fun getNearestBg(date: Long): Double {
|
||||
bgReadingsArray?.let { bgReadingsArray ->
|
||||
for (r in bgReadingsArray.indices) {
|
||||
val reading = bgReadingsArray[r]
|
||||
if (reading.date > date) continue
|
||||
return Profile.fromMgdlToUnits(reading.value, units)
|
||||
}
|
||||
return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, units) else Profile.fromMgdlToUnits(100.0, units)
|
||||
} ?: return Profile.fromMgdlToUnits(100.0, units)
|
||||
}
|
||||
|
||||
fun addActivity(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) {
|
||||
val actArrayHist: MutableList<ScaledDataPoint> = ArrayList()
|
||||
val actArrayPred: MutableList<ScaledDataPoint> = ArrayList()
|
||||
val now = System.currentTimeMillis().toDouble()
|
||||
val actScale = Scale()
|
||||
var total: IobTotal
|
||||
var maxIAValue = 0.0
|
||||
var time = fromTime
|
||||
while (time <= toTime) {
|
||||
val profile = profileFunction.getProfile(time)
|
||||
if (profile == null) {
|
||||
time += 5 * 60 * 1000L
|
||||
continue
|
||||
}
|
||||
total = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile)
|
||||
val act: Double = total.activity
|
||||
if (time <= now) actArrayHist.add(ScaledDataPoint(time, act, actScale)) else actArrayPred.add(ScaledDataPoint(time, act, actScale))
|
||||
maxIAValue = max(maxIAValue, abs(act))
|
||||
time += 5 * 60 * 1000L
|
||||
}
|
||||
addSeries(FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also {
|
||||
it.isDrawBackground = false
|
||||
it.color = resourceHelper.gc(R.color.activity)
|
||||
it.thickness = 3
|
||||
})
|
||||
addSeries(FixedLineGraphSeries(Array(actArrayPred.size) { i -> actArrayPred[i] }).also {
|
||||
it.setCustomPaint(Paint().also { paint ->
|
||||
paint.style = Paint.Style.STROKE
|
||||
paint.strokeWidth = 3f
|
||||
paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f)
|
||||
paint.color = resourceHelper.gc(R.color.activity)
|
||||
})
|
||||
})
|
||||
if (useForScale) {
|
||||
maxY = maxIAValue
|
||||
minY = -maxIAValue
|
||||
}
|
||||
actScale.setMultiplier(maxY * scale / maxIAValue)
|
||||
}
|
||||
|
||||
// scale in % of vertical size (like 0.3)
|
||||
fun addIob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, showPrediction: Boolean) {
|
||||
val iobSeries: FixedLineGraphSeries<ScaledDataPoint?>
|
||||
val iobArray: MutableList<ScaledDataPoint> = ArrayList()
|
||||
var maxIobValueFound = Double.MIN_VALUE
|
||||
var lastIob = 0.0
|
||||
val iobScale = Scale()
|
||||
var time = fromTime
|
||||
while (time <= toTime) {
|
||||
val profile = profileFunction.getProfile(time)
|
||||
var iob = 0.0
|
||||
if (profile != null) iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile).iob
|
||||
if (abs(lastIob - iob) > 0.02) {
|
||||
if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale))
|
||||
iobArray.add(ScaledDataPoint(time, iob, iobScale))
|
||||
maxIobValueFound = max(maxIobValueFound, abs(iob))
|
||||
lastIob = iob
|
||||
}
|
||||
time += 5 * 60 * 1000L
|
||||
}
|
||||
iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also {
|
||||
it.isDrawBackground = true
|
||||
it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50%
|
||||
it.color = resourceHelper.gc(R.color.iob)
|
||||
it.thickness = 3
|
||||
}
|
||||
if (showPrediction) {
|
||||
val autosensData = iobCobCalculatorPlugin.getLastAutosensDataSynchronized("GraphData")
|
||||
val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult()
|
||||
val isTempTarget = treatmentsPlugin.getTempTargetFromHistory(System.currentTimeMillis()) != null
|
||||
val iobPred: MutableList<DataPointWithLabelInterface> = ArrayList()
|
||||
val iobPredArray = iobCobCalculatorPlugin.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
|
||||
for (i in iobPredArray) {
|
||||
iobPred.add(i.setColor(resourceHelper.gc(R.color.iobPredAS)))
|
||||
maxIobValueFound = max(maxIobValueFound, abs(i.iob))
|
||||
}
|
||||
addSeries(PointsWithLabelGraphSeries(Array(iobPred.size) { i -> iobPred[i] }))
|
||||
val iobPred2: MutableList<DataPointWithLabelInterface> = ArrayList()
|
||||
val iobPredArray2 = iobCobCalculatorPlugin.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
|
||||
for (i in iobPredArray2) {
|
||||
iobPred2.add(i.setColor(resourceHelper.gc(R.color.iobPred)))
|
||||
maxIobValueFound = max(maxIobValueFound, abs(i.iob))
|
||||
}
|
||||
addSeries(PointsWithLabelGraphSeries(Array(iobPred2.size) { i -> iobPred2[i] }))
|
||||
aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray))
|
||||
aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray2))
|
||||
}
|
||||
if (useForScale) {
|
||||
maxY = maxIobValueFound
|
||||
minY = -maxIobValueFound
|
||||
}
|
||||
iobScale.setMultiplier(maxY * scale / maxIobValueFound)
|
||||
addSeries(iobSeries)
|
||||
}
|
||||
|
||||
// scale in % of vertical size (like 0.3)
|
||||
fun addCob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) {
|
||||
val minFailOverActiveList: MutableList<DataPointWithLabelInterface> = ArrayList()
|
||||
val cobArray: MutableList<ScaledDataPoint> = ArrayList()
|
||||
var maxCobValueFound = 0.0
|
||||
var lastCob = 0
|
||||
val cobScale = Scale()
|
||||
var time = fromTime
|
||||
while (time <= toTime) {
|
||||
iobCobCalculatorPlugin.getAutosensData(time)?.let { autosensData ->
|
||||
val cob = autosensData.cob.toInt()
|
||||
if (cob != lastCob) {
|
||||
if (autosensData.carbsFromBolus > 0) cobArray.add(ScaledDataPoint(time, lastCob.toDouble(), cobScale))
|
||||
cobArray.add(ScaledDataPoint(time, cob.toDouble(), cobScale))
|
||||
maxCobValueFound = max(maxCobValueFound, cob.toDouble())
|
||||
lastCob = cob
|
||||
}
|
||||
if (autosensData.failoverToMinAbsorbtionRate) {
|
||||
autosensData.setScale(cobScale)
|
||||
autosensData.setChartTime(time)
|
||||
minFailOverActiveList.add(autosensData)
|
||||
}
|
||||
}
|
||||
time += 5 * 60 * 1000L
|
||||
}
|
||||
|
||||
// COB
|
||||
addSeries(FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also {
|
||||
it.isDrawBackground = true
|
||||
it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.cob) //50%
|
||||
it.color = resourceHelper.gc(R.color.cob)
|
||||
it.thickness = 3
|
||||
})
|
||||
if (useForScale) {
|
||||
maxY = maxCobValueFound
|
||||
minY = 0.0
|
||||
}
|
||||
cobScale.setMultiplier(maxY * scale / maxCobValueFound)
|
||||
addSeries(PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] }))
|
||||
}
|
||||
|
||||
// scale in % of vertical size (like 0.3)
|
||||
fun addDeviations(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) {
|
||||
class DeviationDataPoint(x: Double, y: Double, var color: Int, scale: Scale) : ScaledDataPoint(x, y, scale)
|
||||
|
||||
val devArray: MutableList<DeviationDataPoint> = ArrayList()
|
||||
var maxDevValueFound = 0.0
|
||||
val devScale = Scale()
|
||||
var time = fromTime
|
||||
while (time <= toTime) {
|
||||
iobCobCalculatorPlugin.getAutosensData(time)?.let { autosensData ->
|
||||
var color = resourceHelper.gc(R.color.deviationblack) // "="
|
||||
if (autosensData.type == "" || autosensData.type == "non-meal") {
|
||||
if (autosensData.pastSensitivity == "C") color = resourceHelper.gc(R.color.deviationgrey)
|
||||
if (autosensData.pastSensitivity == "+") color = resourceHelper.gc(R.color.deviationgreen)
|
||||
if (autosensData.pastSensitivity == "-") color = resourceHelper.gc(R.color.deviationred)
|
||||
} else if (autosensData.type == "uam") {
|
||||
color = resourceHelper.gc(R.color.uam)
|
||||
} else if (autosensData.type == "csf") {
|
||||
color = resourceHelper.gc(R.color.deviationgrey)
|
||||
}
|
||||
devArray.add(DeviationDataPoint(time.toDouble(), autosensData.deviation, color, devScale))
|
||||
maxDevValueFound = max(maxDevValueFound, abs(autosensData.deviation))
|
||||
}
|
||||
time += 5 * 60 * 1000L
|
||||
}
|
||||
|
||||
// DEVIATIONS
|
||||
addSeries(BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also {
|
||||
it.setValueDependentColor { data: DeviationDataPoint -> data.color }
|
||||
})
|
||||
if (useForScale) {
|
||||
maxY = maxDevValueFound
|
||||
minY = -maxY
|
||||
}
|
||||
devScale.setMultiplier(maxY * scale / maxDevValueFound)
|
||||
}
|
||||
|
||||
// scale in % of vertical size (like 0.3)
|
||||
fun addRatio(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) {
|
||||
val ratioArray: MutableList<ScaledDataPoint> = ArrayList()
|
||||
var maxRatioValueFound = Double.MIN_VALUE
|
||||
var minRatioValueFound = Double.MAX_VALUE
|
||||
val ratioScale = Scale()
|
||||
var time = fromTime
|
||||
while (time <= toTime) {
|
||||
iobCobCalculatorPlugin.getAutosensData(time)?.let { autosensData ->
|
||||
ratioArray.add(ScaledDataPoint(time, autosensData.autosensResult.ratio - 1, ratioScale))
|
||||
maxRatioValueFound = max(maxRatioValueFound, autosensData.autosensResult.ratio - 1)
|
||||
minRatioValueFound = min(minRatioValueFound, autosensData.autosensResult.ratio - 1)
|
||||
}
|
||||
time += 5 * 60 * 1000L
|
||||
}
|
||||
|
||||
// RATIOS
|
||||
addSeries(LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also {
|
||||
it.color = resourceHelper.gc(R.color.ratio)
|
||||
it.thickness = 3
|
||||
})
|
||||
if (useForScale) {
|
||||
maxY = max(maxRatioValueFound, abs(minRatioValueFound))
|
||||
minY = -maxY
|
||||
}
|
||||
ratioScale.setMultiplier(maxY * scale / max(maxRatioValueFound, abs(minRatioValueFound)))
|
||||
}
|
||||
|
||||
// scale in % of vertical size (like 0.3)
|
||||
fun addDeviationSlope(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) {
|
||||
val dsMaxArray: MutableList<ScaledDataPoint> = ArrayList()
|
||||
val dsMinArray: MutableList<ScaledDataPoint> = ArrayList()
|
||||
var maxFromMaxValueFound = 0.0
|
||||
var maxFromMinValueFound = 0.0
|
||||
val dsMaxScale = Scale()
|
||||
val dsMinScale = Scale()
|
||||
var time = fromTime
|
||||
while (time <= toTime) {
|
||||
iobCobCalculatorPlugin.getAutosensData(time)?.let { autosensData ->
|
||||
dsMaxArray.add(ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale))
|
||||
dsMinArray.add(ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale))
|
||||
maxFromMaxValueFound = max(maxFromMaxValueFound, abs(autosensData.slopeFromMaxDeviation))
|
||||
maxFromMinValueFound = max(maxFromMinValueFound, abs(autosensData.slopeFromMinDeviation))
|
||||
}
|
||||
time += 5 * 60 * 1000L
|
||||
}
|
||||
|
||||
// Slopes
|
||||
addSeries(LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also {
|
||||
it.color = resourceHelper.gc(R.color.devslopepos)
|
||||
it.thickness = 3
|
||||
})
|
||||
addSeries(LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also {
|
||||
it.color = resourceHelper.gc(R.color.devslopeneg)
|
||||
it.thickness = 3
|
||||
})
|
||||
if (useForScale) {
|
||||
maxY = max(maxFromMaxValueFound, maxFromMinValueFound)
|
||||
minY = -maxY
|
||||
}
|
||||
dsMaxScale.setMultiplier(maxY * scale / maxFromMaxValueFound)
|
||||
dsMinScale.setMultiplier(maxY * scale / maxFromMinValueFound)
|
||||
}
|
||||
|
||||
// scale in % of vertical size (like 0.3)
|
||||
fun addNowLine(now: Long) {
|
||||
val nowPoints = arrayOf(
|
||||
DataPoint(now.toDouble(), 0.0),
|
||||
DataPoint(now.toDouble(), maxY)
|
||||
)
|
||||
addSeries(LineGraphSeries(nowPoints).also {
|
||||
it.isDrawDataPoints = false
|
||||
// custom paint to make a dotted line
|
||||
it.setCustomPaint(Paint().also { paint ->
|
||||
paint.style = Paint.Style.STROKE
|
||||
paint.strokeWidth = 2f
|
||||
paint.pathEffect = DashPathEffect(floatArrayOf(10f, 20f), 0f)
|
||||
paint.color = Color.WHITE
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fun formatAxis(fromTime: Long, endTime: Long) {
|
||||
graph.viewport.setMaxX(endTime.toDouble())
|
||||
graph.viewport.setMinX(fromTime.toDouble())
|
||||
graph.viewport.isXAxisBoundsManual = true
|
||||
graph.gridLabelRenderer.labelFormatter = TimeAsXAxisLabelFormatter("HH")
|
||||
graph.gridLabelRenderer.numHorizontalLabels = 7 // only 7 because of the space
|
||||
}
|
||||
|
||||
private fun addSeries(s: Series<*>) = series.add(s)
|
||||
|
||||
fun performUpdate() {
|
||||
// clear old data
|
||||
graph.series.clear()
|
||||
|
||||
// add pre calculated series
|
||||
for (s in series) {
|
||||
if (!s.isEmpty) {
|
||||
s.onGraphViewAttached(graph)
|
||||
graph.series.add(s)
|
||||
}
|
||||
}
|
||||
var step = 1.0
|
||||
if (maxY < 1) step = 0.1
|
||||
graph.viewport.setMaxY(Round.ceilTo(maxY, step))
|
||||
graph.viewport.setMinY(Round.floorTo(minY, step))
|
||||
graph.viewport.isYAxisBoundsManual = true
|
||||
|
||||
// draw it
|
||||
graph.onDataChanged(false, false)
|
||||
}
|
||||
}
|
|
@ -23,6 +23,12 @@ public class ScaledDataPoint implements DataPointInterface, Serializable {
|
|||
this.scale = scale;
|
||||
}
|
||||
|
||||
public ScaledDataPoint(long x, double y, Scale scale) {
|
||||
this.x=x;
|
||||
this.y=y;
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
public ScaledDataPoint(Date x, double y, Scale scale) {
|
||||
this.x = x.getTime();
|
||||
this.y = y;
|
||||
|
|
|
@ -64,11 +64,11 @@ public class Notification {
|
|||
public static final int DST_IN_24H = 50;
|
||||
public static final int DISKFULL = 51;
|
||||
public static final int OLDVERSION = 52;
|
||||
public static final int USERMESSAGE = 53;
|
||||
public static final int OVER_24H_TIME_CHANGE_REQUESTED = 54;
|
||||
public static final int INVALID_VERSION = 55;
|
||||
public static final int PERMISSION_SYSTEM_WINDOW = 56;
|
||||
|
||||
public static final int USERMESSAGE = 1000;
|
||||
|
||||
public int id;
|
||||
public long date;
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package info.nightscout.androidaps.plugins.general.overview.notifications
|
||||
|
||||
class NotificationUserMessage (text :String): Notification() {
|
||||
|
||||
init {
|
||||
var hash = text.hashCode()
|
||||
if (hash < USERMESSAGE) hash += USERMESSAGE
|
||||
id = hash
|
||||
this.text = text
|
||||
level = URGENT
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ package info.nightscout.androidaps.plugins.pump.danaR.services;
|
|||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
@ -20,6 +19,9 @@ import dagger.android.HasAndroidInjector;
|
|||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.data.Profile;
|
||||
import info.nightscout.androidaps.data.PumpEnactResult;
|
||||
import info.nightscout.androidaps.events.EventAppExit;
|
||||
import info.nightscout.androidaps.events.EventBTChange;
|
||||
import info.nightscout.androidaps.events.EventPreferenceChange;
|
||||
import info.nightscout.androidaps.events.EventPumpStatusChanged;
|
||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
||||
import info.nightscout.androidaps.logging.LTag;
|
||||
|
@ -41,9 +43,12 @@ import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgPCCommStop;
|
|||
import info.nightscout.androidaps.plugins.pump.danaR.comm.RecordTypes;
|
||||
import info.nightscout.androidaps.plugins.treatments.Treatment;
|
||||
import info.nightscout.androidaps.utils.DateUtil;
|
||||
import info.nightscout.androidaps.utils.FabricPrivacy;
|
||||
import info.nightscout.androidaps.utils.ToastUtils;
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
* Created by mike on 28.01.2018.
|
||||
|
@ -57,6 +62,9 @@ public abstract class AbstractDanaRExecutionService extends DaggerService {
|
|||
@Inject Context context;
|
||||
@Inject ResourceHelper resourceHelper;
|
||||
@Inject DanaRPump danaRPump;
|
||||
@Inject FabricPrivacy fabricPrivacy;
|
||||
|
||||
private CompositeDisposable disposable = new CompositeDisposable();
|
||||
|
||||
protected String mDevName;
|
||||
|
||||
|
@ -101,22 +109,41 @@ public abstract class AbstractDanaRExecutionService extends DaggerService {
|
|||
|
||||
public abstract PumpEnactResult setUserOptions();
|
||||
|
||||
protected BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
String action = intent.getAction();
|
||||
if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
|
||||
aapsLogger.debug(LTag.PUMP, "Device was disconnected " + device.getName());//Device was disconnected
|
||||
if (mBTDevice != null && mBTDevice.getName() != null && mBTDevice.getName().equals(device.getName())) {
|
||||
if (mSerialIOThread != null) {
|
||||
mSerialIOThread.disconnect("BT disconnection broadcast");
|
||||
@Override public void onCreate() {
|
||||
super.onCreate();
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventBTChange.class)
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(event -> {
|
||||
if (event.getState() == EventBTChange.Change.DISCONNECT) {
|
||||
aapsLogger.debug(LTag.PUMP, "Device was disconnected " + event.getDeviceName());//Device was disconnected
|
||||
if (mBTDevice != null && mBTDevice.getName() != null && mBTDevice.getName().equals(event.getDeviceName())) {
|
||||
if (mSerialIOThread != null) {
|
||||
mSerialIOThread.disconnect("BT disconnection broadcast");
|
||||
}
|
||||
rxBus.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED));
|
||||
}
|
||||
}
|
||||
rxBus.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}, fabricPrivacy::logException)
|
||||
);
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventAppExit.class)
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(event -> {
|
||||
aapsLogger.debug(LTag.PUMP, "EventAppExit received");
|
||||
if (mSerialIOThread != null)
|
||||
mSerialIOThread.disconnect("Application exit");
|
||||
stopSelf();
|
||||
}, fabricPrivacy::logException)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
disposable.clear();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
|
|
|
@ -92,42 +92,13 @@ public class DanaRExecutionService extends AbstractDanaRExecutionService {
|
|||
@Inject ProfileFunction profileFunction;
|
||||
@Inject SP sp;
|
||||
|
||||
private CompositeDisposable disposable = new CompositeDisposable();
|
||||
|
||||
public DanaRExecutionService() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mBinder = new LocalBinder();
|
||||
context.registerReceiver(receiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED));
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventPreferenceChange.class)
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(event -> {
|
||||
if (mSerialIOThread != null)
|
||||
mSerialIOThread.disconnect("EventPreferenceChange");
|
||||
}, exception -> FabricPrivacy.getInstance().logException(exception))
|
||||
);
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventAppExit.class)
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(event -> {
|
||||
aapsLogger.debug(LTag.PUMP, "EventAppExit received");
|
||||
if (mSerialIOThread != null)
|
||||
mSerialIOThread.disconnect("Application exit");
|
||||
context.unregisterReceiver(receiver);
|
||||
stopSelf();
|
||||
}, exception -> FabricPrivacy.getInstance().logException(exception))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
disposable.clear();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
public class LocalBinder extends Binder {
|
||||
|
|
|
@ -83,8 +83,6 @@ public class DanaRKoreanExecutionService extends AbstractDanaRExecutionService {
|
|||
@Inject ActivePluginProvider activePlugin;
|
||||
@Inject ProfileFunction profileFunction;
|
||||
|
||||
private CompositeDisposable disposable = new CompositeDisposable();
|
||||
|
||||
public DanaRKoreanExecutionService() {
|
||||
}
|
||||
|
||||
|
@ -92,33 +90,6 @@ public class DanaRKoreanExecutionService extends AbstractDanaRExecutionService {
|
|||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mBinder = new LocalBinder();
|
||||
context.registerReceiver(receiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED));
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventPreferenceChange.class)
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(event -> {
|
||||
if (mSerialIOThread != null)
|
||||
mSerialIOThread.disconnect("EventPreferenceChange");
|
||||
}, exception -> FabricPrivacy.getInstance().logException(exception))
|
||||
);
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventAppExit.class)
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(event -> {
|
||||
aapsLogger.debug(LTag.PUMP, "EventAppExit received");
|
||||
|
||||
if (mSerialIOThread != null)
|
||||
mSerialIOThread.disconnect("Application exit");
|
||||
context.unregisterReceiver(receiver);
|
||||
stopSelf();
|
||||
}, exception -> FabricPrivacy.getInstance().logException(exception))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
disposable.clear();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
public class LocalBinder extends Binder {
|
||||
|
|
|
@ -108,8 +108,6 @@ public class DanaRv2ExecutionService extends AbstractDanaRExecutionService {
|
|||
@Inject ProfileFunction profileFunction;
|
||||
@Inject SP sp;
|
||||
|
||||
private CompositeDisposable disposable = new CompositeDisposable();
|
||||
|
||||
private long lastHistoryFetched = 0;
|
||||
|
||||
public DanaRv2ExecutionService() {
|
||||
|
@ -125,33 +123,6 @@ public class DanaRv2ExecutionService extends AbstractDanaRExecutionService {
|
|||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mBinder = new LocalBinder();
|
||||
context.registerReceiver(receiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED));
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventPreferenceChange.class)
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(event -> {
|
||||
if (mSerialIOThread != null)
|
||||
mSerialIOThread.disconnect("EventPreferenceChange");
|
||||
}, exception -> FabricPrivacy.getInstance().logException(exception))
|
||||
);
|
||||
disposable.add(rxBus
|
||||
.toObservable(EventAppExit.class)
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(event -> {
|
||||
aapsLogger.debug(LTag.PUMP, "EventAppExit received");
|
||||
|
||||
if (mSerialIOThread != null)
|
||||
mSerialIOThread.disconnect("Application exit");
|
||||
context.getApplicationContext().unregisterReceiver(receiver);
|
||||
stopSelf();
|
||||
}, exception -> FabricPrivacy.getInstance().logException(exception))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
disposable.clear();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
|
|
|
@ -37,6 +37,7 @@ import info.nightscout.androidaps.queue.Callback
|
|||
import info.nightscout.androidaps.queue.events.EventQueueChanged
|
||||
import info.nightscout.androidaps.utils.DateUtil
|
||||
import info.nightscout.androidaps.utils.FabricPrivacy
|
||||
import info.nightscout.androidaps.utils.OKDialog
|
||||
import info.nightscout.androidaps.utils.SetWarnColor
|
||||
import info.nightscout.androidaps.utils.T
|
||||
import info.nightscout.androidaps.utils.extensions.plusAssign
|
||||
|
@ -86,13 +87,13 @@ class MedtronicFragment : DaggerFragment() {
|
|||
if (MedtronicUtil.getPumpStatus().verifyConfiguration()) {
|
||||
startActivity(Intent(context, MedtronicHistoryActivity::class.java))
|
||||
} else {
|
||||
MedtronicUtil.displayNotConfiguredDialog(context)
|
||||
displayNotConfiguredDialog()
|
||||
}
|
||||
}
|
||||
|
||||
medtronic_refresh.setOnClickListener {
|
||||
if (!MedtronicUtil.getPumpStatus().verifyConfiguration()) {
|
||||
MedtronicUtil.displayNotConfiguredDialog(context)
|
||||
displayNotConfiguredDialog()
|
||||
} else {
|
||||
medtronic_refresh.isEnabled = false
|
||||
medtronicPumpPlugin.resetStatusState()
|
||||
|
@ -108,7 +109,7 @@ class MedtronicFragment : DaggerFragment() {
|
|||
if (MedtronicUtil.getPumpStatus().verifyConfiguration()) {
|
||||
startActivity(Intent(context, RileyLinkStatusActivity::class.java))
|
||||
} else {
|
||||
MedtronicUtil.displayNotConfiguredDialog(context)
|
||||
displayNotConfiguredDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -247,6 +248,13 @@ class MedtronicFragment : DaggerFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun displayNotConfiguredDialog() {
|
||||
context?.let {
|
||||
OKDialog.show(it, resourceHelper.gs(R.string.combo_warning),
|
||||
resourceHelper.gs(R.string.medtronic_error_operation_not_possible_no_configuration), null)
|
||||
}
|
||||
}
|
||||
|
||||
// GUI functions
|
||||
@Synchronized
|
||||
fun updateGUI() {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.ui;
|
||||
|
||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.logging.LTag;
|
||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
||||
|
@ -43,8 +42,7 @@ public class MedtronicUIComm {
|
|||
|
||||
public synchronized MedtronicUITask executeCommand(MedtronicCommandType commandType, Object... parameters) {
|
||||
|
||||
if (isLogEnabled())
|
||||
aapsLogger.warn(LTag.PUMP, "Execute Command: " + commandType.name());
|
||||
aapsLogger.warn(LTag.PUMP, "Execute Command: " + commandType.name());
|
||||
|
||||
MedtronicUITask task = new MedtronicUITask(commandType, parameters);
|
||||
|
||||
|
@ -78,7 +76,7 @@ public class MedtronicUIComm {
|
|||
// }
|
||||
// }
|
||||
|
||||
if (!task.isReceived() && isLogEnabled()) {
|
||||
if (!task.isReceived()) {
|
||||
aapsLogger.warn(LTag.PUMP, "Reply not received for " + commandType);
|
||||
}
|
||||
|
||||
|
@ -112,9 +110,4 @@ public class MedtronicUIComm {
|
|||
public void startTunning() {
|
||||
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.IPC.MSG_PUMP_tunePump);
|
||||
}
|
||||
|
||||
private boolean isLogEnabled() {
|
||||
return L.isEnabled(L.PUMP);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -61,10 +61,6 @@ public class RileyLinkMedtronicService extends RileyLinkService {
|
|||
}
|
||||
|
||||
|
||||
public static MedtronicCommunicationManager getCommunicationManager() {
|
||||
return instance.medtronicCommunicationManager;
|
||||
}
|
||||
|
||||
@Override public void onCreate() {
|
||||
super.onCreate();
|
||||
aapsLogger.debug(LTag.PUMPCOMM, "RileyLinkMedtronicService newly created");
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
package info.nightscout.androidaps.plugins.pump.medtronic.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import org.joda.time.LocalTime;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
@ -15,8 +12,6 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.interfaces.PluginType;
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.logging.StacktraceLoggerWrapper;
|
||||
|
@ -41,7 +36,6 @@ import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState;
|
|||
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicDeviceStatusChange;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.service.RileyLinkMedtronicService;
|
||||
import info.nightscout.androidaps.utils.OKDialog;
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
||||
|
||||
/**
|
||||
|
@ -532,9 +526,4 @@ public class MedtronicUtil extends RileyLinkUtil {
|
|||
}
|
||||
|
||||
|
||||
public static void displayNotConfiguredDialog(Context context) {
|
||||
OKDialog.show(context, MainApp.gs(R.string.combo_warning),
|
||||
MainApp.gs(R.string.medtronic_error_operation_not_possible_no_configuration), null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.NSUpload;
|
|||
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification;
|
||||
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification;
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.MedtronicHistoryData;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
||||
import info.nightscout.androidaps.utils.DateUtil;
|
||||
|
@ -571,7 +572,7 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface
|
|||
// return true if new record is created
|
||||
@Override
|
||||
public boolean addToHistoryTreatment(DetailedBolusInfo detailedBolusInfo, boolean allowUpdate) {
|
||||
boolean medtronicPump = MedtronicUtil.isMedtronicPump();
|
||||
boolean medtronicPump = activePlugin.getActivePump() instanceof MedtronicPumpPlugin;
|
||||
|
||||
getAapsLogger().debug(MedtronicHistoryData.doubleBolusDebug, LTag.DATATREATMENTS, "DoubleBolusDebug: addToHistoryTreatment::isMedtronicPump={} " + medtronicPump);
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package info.nightscout.androidaps.receivers
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import dagger.android.DaggerBroadcastReceiver
|
||||
import info.nightscout.androidaps.events.EventBTChange
|
||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
||||
import javax.inject.Inject
|
||||
|
||||
class BTReceiver : DaggerBroadcastReceiver() {
|
||||
@Inject lateinit var rxBus: RxBusWrapper
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
super.onReceive(context, intent)
|
||||
val device : BluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
|
||||
|
||||
when (intent.action) {
|
||||
BluetoothDevice.ACTION_ACL_CONNECTED ->
|
||||
rxBus.send(EventBTChange(EventBTChange.Change.CONNECT, device.name))
|
||||
BluetoothDevice.ACTION_ACL_DISCONNECTED ->
|
||||
rxBus.send(EventBTChange(EventBTChange.Change.DISCONNECT, device.name))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -59,6 +59,7 @@ class SWDefinition @Inject constructor(
|
|||
private val nsClientPlugin: NSClientPlugin,
|
||||
private val nsProfilePlugin: NSProfilePlugin,
|
||||
private val protectionCheck: ProtectionCheck,
|
||||
private val importExportPrefs: ImportExportPrefs,
|
||||
private val androidPermission: AndroidPermission
|
||||
) {
|
||||
|
||||
|
@ -160,8 +161,8 @@ class SWDefinition @Inject constructor(
|
|||
.add(SWBreak(injector))
|
||||
.add(SWButton(injector)
|
||||
.text(R.string.nav_import)
|
||||
.action(Runnable { ImportExportPrefs.importSharedPreferences(activity) }))
|
||||
.visibility(SWValidator { ImportExportPrefs.file.exists() && !androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) })
|
||||
.action(Runnable { importExportPrefs.importSharedPreferences(activity) }))
|
||||
.visibility(SWValidator { importExportPrefs.prefsFileExists() && !androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) })
|
||||
private val screenNsClient = SWScreen(injector, R.string.nsclientinternal_title)
|
||||
.skippable(true)
|
||||
.add(SWInfotext(injector)
|
||||
|
|
|
@ -4,17 +4,10 @@ import android.annotation.SuppressLint
|
|||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Handler
|
||||
import android.os.SystemClock
|
||||
import android.text.Spanned
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import info.nightscout.androidaps.MainApp
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.utils.alertDialogs.AlertDialogHelper
|
||||
|
||||
object OKDialog {
|
||||
@SuppressLint("InflateParams")
|
||||
|
@ -23,11 +16,9 @@ object OKDialog {
|
|||
fun show(context: Context, title: String, message: String, runnable: Runnable? = null) {
|
||||
var notEmptytitle = title
|
||||
if (notEmptytitle.isEmpty()) notEmptytitle = context.getString(R.string.message)
|
||||
val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null)
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_title) as TextView).text = notEmptytitle
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp)
|
||||
AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme))
|
||||
.setCustomTitle(titleLayout)
|
||||
|
||||
AlertDialogHelper.Builder(context)
|
||||
.setCustomTitle(AlertDialogHelper.buildCustomTitle(context, notEmptytitle))
|
||||
.setMessage(message)
|
||||
.setPositiveButton(context.getString(R.string.ok)) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
|
@ -38,23 +29,15 @@ object OKDialog {
|
|||
.setCanceledOnTouchOutside(false)
|
||||
}
|
||||
|
||||
fun runOnUiThread(theRunnable: Runnable?) {
|
||||
@Suppress("DEPRECATION")
|
||||
val mainHandler = Handler(MainApp.instance().applicationContext.mainLooper)
|
||||
theRunnable?.let { mainHandler.post(it) }
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun show(activity: Activity, title: String, message: Spanned, runnable: Runnable? = null) {
|
||||
var notEmptytitle = title
|
||||
if (notEmptytitle.isEmpty()) notEmptytitle = activity.getString(R.string.message)
|
||||
val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null)
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_title) as TextView).text = notEmptytitle
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp)
|
||||
AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme))
|
||||
.setCustomTitle(titleLayout)
|
||||
|
||||
AlertDialogHelper.Builder(activity)
|
||||
.setCustomTitle(AlertDialogHelper.buildCustomTitle(activity, notEmptytitle))
|
||||
.setMessage(message)
|
||||
.setPositiveButton(activity.getString(R.string.ok)) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
|
@ -79,12 +62,9 @@ object OKDialog {
|
|||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun showConfirmation(activity: Activity, title: String, message: Spanned, ok: Runnable?, cancel: Runnable? = null) {
|
||||
val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null)
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_title) as TextView).text = title
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp)
|
||||
AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme))
|
||||
AlertDialogHelper.Builder(activity)
|
||||
.setMessage(message)
|
||||
.setCustomTitle(titleLayout)
|
||||
.setCustomTitle(AlertDialogHelper.buildCustomTitle(activity, title))
|
||||
.setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
SystemClock.sleep(100)
|
||||
|
@ -103,12 +83,9 @@ object OKDialog {
|
|||
@SuppressLint("InflateParams")
|
||||
@JvmStatic
|
||||
fun showConfirmation(activity: Activity, title: String, message: String, ok: Runnable?, cancel: Runnable? = null) {
|
||||
val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null)
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_title) as TextView).text = title
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp)
|
||||
AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme))
|
||||
AlertDialogHelper.Builder(activity)
|
||||
.setMessage(message)
|
||||
.setCustomTitle(titleLayout)
|
||||
.setCustomTitle(AlertDialogHelper.buildCustomTitle(activity, title))
|
||||
.setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
SystemClock.sleep(100)
|
||||
|
@ -133,12 +110,9 @@ object OKDialog {
|
|||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun showConfirmation(context: Context, title: String, message: Spanned, ok: Runnable?, cancel: Runnable? = null) {
|
||||
val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null)
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_title) as TextView).text = title
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp)
|
||||
AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme))
|
||||
AlertDialogHelper.Builder(context)
|
||||
.setMessage(message)
|
||||
.setCustomTitle(titleLayout)
|
||||
.setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title))
|
||||
.setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
SystemClock.sleep(100)
|
||||
|
@ -164,12 +138,9 @@ object OKDialog {
|
|||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun showConfirmation(context: Context, title: String, message: String, ok: Runnable?, cancel: Runnable? = null) {
|
||||
val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null)
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_title) as TextView).text = title
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp)
|
||||
AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme))
|
||||
AlertDialogHelper.Builder(context)
|
||||
.setMessage(message)
|
||||
.setCustomTitle(titleLayout)
|
||||
.setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title))
|
||||
.setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
SystemClock.sleep(100)
|
||||
|
@ -188,12 +159,9 @@ object OKDialog {
|
|||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun showConfirmation(context: Context, title: String, message: String, ok: DialogInterface.OnClickListener?, cancel: DialogInterface.OnClickListener? = null) {
|
||||
val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null)
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_title) as TextView).text = title
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp)
|
||||
AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme))
|
||||
AlertDialogHelper.Builder(context)
|
||||
.setMessage(message)
|
||||
.setCustomTitle(titleLayout)
|
||||
.setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title))
|
||||
.setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int ->
|
||||
dialog.dismiss()
|
||||
SystemClock.sleep(100)
|
||||
|
|
|
@ -1,26 +1,94 @@
|
|||
package info.nightscout.androidaps.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.media.MediaPlayer;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.appcompat.view.ContextThemeWrapper;
|
||||
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.plugins.bus.RxBus;
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
|
||||
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification;
|
||||
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification;
|
||||
|
||||
|
||||
public class ToastUtils {
|
||||
|
||||
public static class Long {
|
||||
|
||||
public static void warnToast(final Context ctx, final String string) {
|
||||
graphicalToast(ctx, string, R.drawable.ic_toast_warn, false);
|
||||
}
|
||||
|
||||
public static void infoToast(final Context ctx, final String string) {
|
||||
graphicalToast(ctx, string, R.drawable.ic_toast_info,false);
|
||||
}
|
||||
|
||||
public static void okToast(final Context ctx, final String string) {
|
||||
graphicalToast(ctx, string, R.drawable.ic_toast_check,false);
|
||||
}
|
||||
|
||||
public static void errorToast(final Context ctx, final String string) {
|
||||
graphicalToast(ctx, string, R.drawable.ic_toast_error,false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void showToastInUiThread(final Context ctx, final int stringId) {
|
||||
showToastInUiThread(ctx, MainApp.gs(stringId));
|
||||
}
|
||||
|
||||
public static void warnToast(final Context ctx, final String string) {
|
||||
graphicalToast(ctx, string, R.drawable.ic_toast_warn, true);
|
||||
}
|
||||
|
||||
public static void infoToast(final Context ctx, final String string) {
|
||||
graphicalToast(ctx, string, R.drawable.ic_toast_info, true);
|
||||
}
|
||||
|
||||
public static void okToast(final Context ctx, final String string) {
|
||||
graphicalToast(ctx, string, R.drawable.ic_toast_check, true);
|
||||
}
|
||||
|
||||
public static void errorToast(final Context ctx, final String string) {
|
||||
graphicalToast(ctx, string, R.drawable.ic_toast_error, true);
|
||||
}
|
||||
|
||||
public static void graphicalToast(final Context ctx, final String string, @DrawableRes int iconId) {
|
||||
graphicalToast(ctx, string, iconId, true);
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
public static void graphicalToast(final Context ctx, final String string, @DrawableRes int iconId, boolean isShort) {
|
||||
Handler mainThread = new Handler(Looper.getMainLooper());
|
||||
mainThread.post(() -> {
|
||||
View toastRoot =LayoutInflater.from(new ContextThemeWrapper(ctx, R.style.AppTheme)).inflate(R.layout.toast, null);
|
||||
TextView toastMessage = toastRoot.findViewById(android.R.id.message);
|
||||
toastMessage.setText(string);
|
||||
|
||||
ImageView toastIcon = toastRoot.findViewById(android.R.id.icon);
|
||||
toastIcon.setImageResource(iconId);
|
||||
|
||||
Toast toast = new Toast(ctx);
|
||||
toast.setDuration(isShort ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG);
|
||||
toast.setView(toastRoot);
|
||||
toast.show();
|
||||
});
|
||||
}
|
||||
|
||||
public static void showToastInUiThread(final Context ctx, final String string) {
|
||||
Handler mainThread = new Handler(Looper.getMainLooper());
|
||||
mainThread.post(() -> Toast.makeText(ctx, string, Toast.LENGTH_SHORT).show());
|
||||
mainThread.post(() -> {
|
||||
Toast.makeText(ctx, string, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
|
||||
public static void showToastInUiThread(final Context ctx, final RxBusWrapper rxBus,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package info.nightscout.androidaps.utils
|
||||
|
||||
import android.os.Handler
|
||||
import android.view.View
|
||||
import info.nightscout.androidaps.MainApp
|
||||
|
||||
/**
|
||||
* Created by adrian on 2019-12-20.
|
||||
|
@ -8,3 +10,7 @@ import android.view.View
|
|||
|
||||
fun Boolean.toVisibility() = if (this) View.VISIBLE else View.GONE
|
||||
|
||||
fun runOnUiThread(theRunnable: Runnable?) {
|
||||
val mainHandler = Handler(MainApp.instance().applicationContext.mainLooper)
|
||||
theRunnable?.let { mainHandler.post(it) }
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package info.nightscout.androidaps.utils.alertDialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import info.nightscout.androidaps.R
|
||||
|
||||
object AlertDialogHelper {
|
||||
|
||||
@Suppress("FunctionName")
|
||||
fun Builder(context: Context, @StyleRes themeResId: Int = R.style.AppTheme) =
|
||||
AlertDialog.Builder(ContextThemeWrapper(context, themeResId))
|
||||
|
||||
fun buildCustomTitle(context: Context, title: String,
|
||||
@DrawableRes iconResource: Int = R.drawable.ic_check_while_48dp,
|
||||
@StyleRes themeResId: Int = R.style.AppTheme,
|
||||
@LayoutRes layoutResource: Int = R.layout.dialog_alert_custom): View? {
|
||||
val titleLayout = LayoutInflater.from(ContextThemeWrapper(context, themeResId)).inflate(layoutResource, null)
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_title) as TextView).text = title
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_icon) as ImageView).setImageResource(iconResource)
|
||||
return titleLayout
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package info.nightscout.androidaps.utils.alertDialogs
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.Color
|
||||
import android.os.SystemClock
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.webkit.WebView
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.TableLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.Prefs
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsStatus
|
||||
import info.nightscout.androidaps.utils.ToastUtils
|
||||
import info.nightscout.androidaps.utils.runOnUiThread
|
||||
import java.util.*
|
||||
|
||||
object PrefImportSummaryDialog {
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
fun showSummary(context: Context, importOk: Boolean, importPossible: Boolean, prefs: Prefs, ok: (() -> Unit)?, cancel: (() -> Unit)? = null) {
|
||||
|
||||
@StyleRes val theme: Int = if (importOk) R.style.AppTheme else {
|
||||
if (importPossible) R.style.AppThemeWarningDialog else R.style.AppThemeErrorDialog
|
||||
}
|
||||
|
||||
@StringRes val messageRes: Int = if (importOk) R.string.check_preferences_before_import else {
|
||||
if (importPossible) R.string.check_preferences_dangerous_import else R.string.check_preferences_cannot_import
|
||||
}
|
||||
|
||||
@DrawableRes val headerIcon: Int = if (importOk) R.drawable.ic_header_import else {
|
||||
if (importPossible) R.drawable.ic_header_warning else R.drawable.ic_header_error
|
||||
}
|
||||
|
||||
val themedCtx = ContextThemeWrapper(context, theme)
|
||||
|
||||
val innerLayout = LayoutInflater.from(themedCtx).inflate(R.layout.dialog_alert_import_summary, null)
|
||||
val table = (innerLayout.findViewById<View>(R.id.summary_table) as TableLayout)
|
||||
val detailsBtn = (innerLayout.findViewById<View>(R.id.summary_details_btn) as Button)
|
||||
|
||||
var idx = 0
|
||||
val details = LinkedList<String>()
|
||||
|
||||
|
||||
for ((metaKey, metaEntry) in prefs.metadata) {
|
||||
val rowLayout = LayoutInflater.from(themedCtx).inflate(R.layout.import_summary_item, null)
|
||||
val label = (rowLayout.findViewById<View>(R.id.summary_text) as TextView)
|
||||
label.setText(metaKey.formatForDisplay(context, metaEntry.value))
|
||||
(rowLayout.findViewById<View>(R.id.summary_icon) as ImageView).setImageResource(metaKey.icon)
|
||||
(rowLayout.findViewById<View>(R.id.status_icon) as ImageView).setImageResource(metaEntry.status.icon)
|
||||
|
||||
if (metaEntry.status == PrefsStatus.WARN) label.setTextColor(themedCtx.getColor(R.color.metadataTextWarning))
|
||||
else if (metaEntry.status == PrefsStatus.ERROR) label.setTextColor(themedCtx.getColor(R.color.metadataTextError))
|
||||
|
||||
if (metaEntry.info != null) {
|
||||
details.add("<b>${context.getString(metaKey.label)}</b>: ${metaEntry.value}<br/><i style=\"color:silver\">${metaEntry.info}</i>")
|
||||
rowLayout.isClickable = true
|
||||
rowLayout.setOnClickListener {
|
||||
val msg = "[${context.getString(metaKey.label)}] ${metaEntry.info}"
|
||||
when (metaEntry.status) {
|
||||
PrefsStatus.WARN -> ToastUtils.Long.warnToast(context, msg)
|
||||
PrefsStatus.ERROR -> ToastUtils.Long.errorToast(context, msg)
|
||||
else -> ToastUtils.Long.infoToast(context, msg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rowLayout.isClickable = true
|
||||
rowLayout.setOnClickListener { ToastUtils.Long.infoToast(context, context.getString(metaKey.label)) }
|
||||
}
|
||||
|
||||
table.addView(rowLayout, idx++)
|
||||
}
|
||||
|
||||
if (details.size > 0) {
|
||||
detailsBtn.visibility = View.VISIBLE
|
||||
detailsBtn.setOnClickListener {
|
||||
val detailsLayout = LayoutInflater.from(context).inflate(R.layout.import_summary_details, null)
|
||||
val wview = detailsLayout.findViewById<View>(R.id.details_webview) as WebView
|
||||
wview.loadData("<!doctype html><html><head><meta charset=\"utf-8\"><style>body { color: white; }</style></head><body>" + details.joinToString("<hr>"), "text/html; charset=utf-8", "utf-8");
|
||||
wview.setBackgroundColor(Color.TRANSPARENT);
|
||||
wview.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null);
|
||||
|
||||
AlertDialogHelper.Builder(context, R.style.AppTheme)
|
||||
.setCustomTitle(AlertDialogHelper.buildCustomTitle(
|
||||
context,
|
||||
context.getString(R.string.check_preferences_details_title),
|
||||
R.drawable.ic_header_log,
|
||||
R.style.AppTheme))
|
||||
.setView(detailsLayout)
|
||||
.setPositiveButton(android.R.string.ok) { dialogInner: DialogInterface, _: Int ->
|
||||
dialogInner.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
val builder = AlertDialogHelper.Builder(context, theme)
|
||||
.setMessage(context.getString(messageRes))
|
||||
.setCustomTitle(AlertDialogHelper.buildCustomTitle(context, context.getString(R.string.nav_import), headerIcon, theme))
|
||||
.setView(innerLayout)
|
||||
|
||||
.setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
SystemClock.sleep(100)
|
||||
if (cancel != null) {
|
||||
runOnUiThread(Runnable {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (importPossible) {
|
||||
builder.setPositiveButton(
|
||||
if (importOk) R.string.check_preferences_import_btn else R.string.check_preferences_import_anyway_btn
|
||||
) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
SystemClock.sleep(100)
|
||||
if (ok != null) {
|
||||
runOnUiThread(Runnable {
|
||||
ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dialog = builder.show()
|
||||
dialog.setCanceledOnTouchOutside(false)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package info.nightscout.androidaps.utils.alertDialogs
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.SystemClock
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.utils.runOnUiThread
|
||||
|
||||
object TwoMessagesAlertDialog {
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
fun showAlert(context: Context, title: String, message: String, secondMessage: String, ok: (() -> Unit)?, cancel: (() -> Unit)? = null, @DrawableRes icon: Int? = null) {
|
||||
|
||||
val secondMessageLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_two_messages, null)
|
||||
(secondMessageLayout.findViewById<View>(R.id.password_prompt_title) as TextView).text = secondMessage
|
||||
|
||||
val dialog = AlertDialogHelper.Builder(context)
|
||||
.setMessage(message)
|
||||
.setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title, icon
|
||||
?: R.drawable.ic_check_while_48dp))
|
||||
.setView(secondMessageLayout)
|
||||
.setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
SystemClock.sleep(100)
|
||||
if (ok != null) {
|
||||
runOnUiThread(Runnable {
|
||||
ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
SystemClock.sleep(100)
|
||||
if (cancel != null) {
|
||||
runOnUiThread(Runnable {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
}
|
||||
.show()
|
||||
dialog.setCanceledOnTouchOutside(false)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package info.nightscout.androidaps.utils.alertDialogs
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.SystemClock
|
||||
import androidx.annotation.StringRes
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.utils.runOnUiThread
|
||||
|
||||
// if you need error dialog - duplicate to ErrorDialog and make it and use: AppThemeErrorDialog & R.drawable.ic_header_error instead
|
||||
|
||||
object WarningDialog {
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun showWarning(context: Context, title: String, message: String, @StringRes positiveButton: Int = -1, ok: (() -> Unit)? = null, cancel: (() -> Unit)? = null) {
|
||||
|
||||
val builder = AlertDialogHelper.Builder(context, R.style.AppThemeWarningDialog)
|
||||
.setMessage(message)
|
||||
.setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title, R.drawable.ic_header_warning, R.style.AppThemeWarningDialog))
|
||||
.setNegativeButton(R.string.dismiss) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
SystemClock.sleep(100)
|
||||
if (cancel != null) {
|
||||
runOnUiThread(Runnable {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (positiveButton != -1) {
|
||||
builder.setPositiveButton(positiveButton) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
SystemClock.sleep(100)
|
||||
if (ok != null) {
|
||||
runOnUiThread(Runnable {
|
||||
ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dialog = builder.show()
|
||||
dialog.setCanceledOnTouchOutside(true)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +1,16 @@
|
|||
package info.nightscout.androidaps.utils.protection
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.utils.CryptoUtil
|
||||
import info.nightscout.androidaps.utils.ToastUtils
|
||||
import info.nightscout.androidaps.utils.alertDialogs.AlertDialogHelper
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
@ -26,41 +22,36 @@ val AUTOFILL_HINT_NEW_PASSWORD = "newPassword"
|
|||
class PasswordCheck @Inject constructor(val sp: SP) {
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
fun queryPassword(activity: FragmentActivity, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)?, cancel: (()->Unit)? = null, fail: (()->Unit)? = null) {
|
||||
fun queryPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)?, cancel: (()->Unit)? = null, fail: (()->Unit)? = null) {
|
||||
val password = sp.getString(preference, "")
|
||||
if (password == "") {
|
||||
ok?.invoke("")
|
||||
return
|
||||
}
|
||||
|
||||
val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null)
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_title) as TextView).text = activity.getString(labelId)
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_key_48dp)
|
||||
|
||||
val promptsView = LayoutInflater.from(activity).inflate(R.layout.passwordprompt, null)
|
||||
|
||||
val alertDialogBuilder = AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme))
|
||||
val promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null)
|
||||
val alertDialogBuilder = AlertDialogHelper.Builder(context)
|
||||
alertDialogBuilder.setView(promptsView)
|
||||
|
||||
val userInput = promptsView.findViewById<View>(R.id.passwordprompt_pass) as EditText
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val autoFillHintPasswordKind = activity.getString(preference)
|
||||
val autoFillHintPasswordKind = context.getString(preference)
|
||||
userInput.setAutofillHints(View.AUTOFILL_HINT_PASSWORD, "aaps_${autoFillHintPasswordKind}")
|
||||
userInput.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_YES
|
||||
}
|
||||
|
||||
alertDialogBuilder
|
||||
.setCancelable(false)
|
||||
.setCustomTitle(titleLayout)
|
||||
.setPositiveButton(activity.getString(R.string.ok)) { _, _ ->
|
||||
.setCustomTitle(AlertDialogHelper.buildCustomTitle(context, context.getString(labelId), R.drawable.ic_header_key))
|
||||
.setPositiveButton(context.getString(R.string.ok)) { _, _ ->
|
||||
val enteredPassword = userInput.text.toString()
|
||||
if (CryptoUtil.checkPassword(enteredPassword, password)) ok?.invoke(enteredPassword)
|
||||
else {
|
||||
ToastUtils.showToastInUiThread(activity, activity.getString(R.string.wrongpassword))
|
||||
ToastUtils.errorToast(context, context.getString(R.string.wrongpassword))
|
||||
fail?.invoke()
|
||||
}
|
||||
}
|
||||
.setNegativeButton(activity.getString(R.string.cancel)
|
||||
.setNegativeButton(context.getString(R.string.cancel)
|
||||
) { dialog, _ ->
|
||||
cancel?.invoke()
|
||||
dialog.cancel()
|
||||
|
@ -72,12 +63,7 @@ class PasswordCheck @Inject constructor(val sp: SP) {
|
|||
@SuppressLint("InflateParams")
|
||||
fun setPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)? = null, cancel: (()->Unit)? = null, clear: (()->Unit)? = null) {
|
||||
val promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null)
|
||||
|
||||
val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null)
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_title) as TextView).text = context.getText(labelId)
|
||||
(titleLayout.findViewById<View>(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_key_48dp)
|
||||
|
||||
val alertDialogBuilder = AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme))
|
||||
val alertDialogBuilder = AlertDialogHelper.Builder(context)
|
||||
alertDialogBuilder.setView(promptsView)
|
||||
|
||||
val userInput = promptsView.findViewById<View>(R.id.passwordprompt_pass) as EditText
|
||||
|
@ -90,20 +76,20 @@ class PasswordCheck @Inject constructor(val sp: SP) {
|
|||
|
||||
alertDialogBuilder
|
||||
.setCancelable(false)
|
||||
.setCustomTitle(titleLayout)
|
||||
.setCustomTitle(AlertDialogHelper.buildCustomTitle(context, context.getString(labelId), R.drawable.ic_header_key))
|
||||
.setPositiveButton(context.getString(R.string.ok)) { _, _ ->
|
||||
val enteredPassword = userInput.text.toString()
|
||||
if (enteredPassword.isNotEmpty()) {
|
||||
sp.putString(preference, CryptoUtil.hashPassword(enteredPassword))
|
||||
ToastUtils.showToastInUiThread(context, context.getString(R.string.password_set))
|
||||
ToastUtils.okToast(context, context.getString(R.string.password_set))
|
||||
ok?.invoke(enteredPassword)
|
||||
} else {
|
||||
if (sp.contains(preference)) {
|
||||
sp.remove(preference)
|
||||
ToastUtils.showToastInUiThread(context, context.getString(R.string.password_cleared))
|
||||
ToastUtils.graphicalToast(context, context.getString(R.string.password_cleared), R.drawable.ic_toast_delete_confirm)
|
||||
clear?.invoke()
|
||||
} else {
|
||||
ToastUtils.showToastInUiThread(context, context.getString(R.string.password_not_changed))
|
||||
ToastUtils.warnToast(context, context.getString(R.string.password_not_changed))
|
||||
cancel?.invoke()
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +97,7 @@ class PasswordCheck @Inject constructor(val sp: SP) {
|
|||
}
|
||||
.setNegativeButton(context.getString(R.string.cancel)
|
||||
) { dialog, _ ->
|
||||
ToastUtils.showToastInUiThread(context, context.getString(R.string.password_not_changed))
|
||||
ToastUtils.infoToast(context, context.getString(R.string.password_not_changed))
|
||||
cancel?.invoke()
|
||||
dialog.cancel()
|
||||
}
|
||||
|
|
|
@ -0,0 +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)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +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)
|
||||
|
||||
}
|
15
app/src/main/res/drawable/alert_border_error.xml
Normal file
15
app/src/main/res/drawable/alert_border_error.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="PrivateResource"
|
||||
android:insetLeft="16dp"
|
||||
android:insetTop="16dp"
|
||||
android:insetRight="16dp"
|
||||
android:insetBottom="16dp">
|
||||
<shape
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="2dp" />
|
||||
<solid android:color="@color/background_floating_material_dark" />
|
||||
<stroke android:color="@color/errorAlertBackground" android:width="3dp" />
|
||||
</shape></inset>
|
||||
|
15
app/src/main/res/drawable/alert_border_warning.xml
Normal file
15
app/src/main/res/drawable/alert_border_warning.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="PrivateResource"
|
||||
android:insetLeft="16dp"
|
||||
android:insetTop="16dp"
|
||||
android:insetRight="16dp"
|
||||
android:insetBottom="16dp">
|
||||
<shape
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="2dp" />
|
||||
<solid android:color="@color/background_floating_material_dark" />
|
||||
<stroke android:color="@color/warningAlertBackground" android:width="3dp" />
|
||||
</shape></inset>
|
||||
|
16
app/src/main/res/drawable/ic_header_error.xml
Normal file
16
app/src/main/res/drawable/ic_header_error.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12"
|
||||
android:scaleX="0.8"
|
||||
android:scaleY="0.8">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M8.27,3L3,8.27V15.73L8.27,21H15.73C17.5,19.24 21,15.73 21,15.73V8.27L15.73,3M9.1,5H14.9L19,9.1V14.9L14.9,19H9.1L5,14.9V9.1M9.12,7.71L7.71,9.12L10.59,12L7.71,14.88L9.12,16.29L12,13.41L14.88,16.29L16.29,14.88L13.41,12L16.29,9.12L14.88,7.71L12,10.59" />
|
||||
</group>
|
||||
</vector>
|
16
app/src/main/res/drawable/ic_header_export.xml
Normal file
16
app/src/main/res/drawable/ic_header_export.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12"
|
||||
android:scaleX="0.66"
|
||||
android:scaleY="0.66">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z" />
|
||||
</group>
|
||||
</vector>
|
16
app/src/main/res/drawable/ic_header_import.xml
Normal file
16
app/src/main/res/drawable/ic_header_import.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12"
|
||||
android:scaleX="0.66"
|
||||
android:scaleY="0.66">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M14,12L10,8V11H2V13H10V16M20,18V6C20,4.89 19.1,4 18,4H6A2,2 0 0,0 4,6V9H6V6H18V18H6V15H4V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18Z" />
|
||||
</group>
|
||||
</vector>
|
16
app/src/main/res/drawable/ic_header_key.xml
Normal file
16
app/src/main/res/drawable/ic_header_key.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12"
|
||||
android:scaleX="0.66"
|
||||
android:scaleY="0.66">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M22 18V22H18V19H15V16H12L9.74 13.74C9.19 13.91 8.61 14 8 14A6 6 0 0 1 2 8A6 6 0 0 1 8 2A6 6 0 0 1 14 8C14 8.61 13.91 9.19 13.74 9.74L22 18M7 5A2 2 0 0 0 5 7A2 2 0 0 0 7 9A2 2 0 0 0 9 7A2 2 0 0 0 7 5Z" />
|
||||
</group>
|
||||
</vector>
|
16
app/src/main/res/drawable/ic_header_log.xml
Normal file
16
app/src/main/res/drawable/ic_header_log.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12"
|
||||
android:scaleX="0.8"
|
||||
android:scaleY="0.8">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M15,20A1,1 0 0,0 16,19V4H8A1,1 0 0,0 7,5V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H20V5A1,1 0 0,0 19,4A1,1 0 0,0 18,5V9L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H13A2,2 0 0,0 15,20M9,6H14V8H9V6M9,10H14V12H9V10M9,14H14V16H9V14Z" />
|
||||
</group>
|
||||
</vector>
|
16
app/src/main/res/drawable/ic_header_warning.xml
Normal file
16
app/src/main/res/drawable/ic_header_warning.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12"
|
||||
android:scaleX="0.75"
|
||||
android:scaleY="0.75">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16" />
|
||||
</group>
|
||||
</vector>
|
|
@ -1,17 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="#FFFFFF"
|
||||
>
|
||||
<group
|
||||
android:scaleX="0.66"
|
||||
android:scaleY="0.66"
|
||||
android:pivotX="12"
|
||||
android:pivotY="12">
|
||||
<path
|
||||
android:pathData="M22 18V22H18V19H15V16H12L9.74 13.74C9.19 13.91 8.61 14 8 14A6 6 0 0 1 2 8A6 6 0 0 1 8 2A6 6 0 0 1 14 8C14 8.61 13.91 9.19 13.74 9.74L22 18M7 5A2 2 0 0 0 5 7A2 2 0 0 0 7 9A2 2 0 0 0 9 7A2 2 0 0 0 7 5Z"
|
||||
android:fillColor="#FF000000" />
|
||||
</group>
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_meta_date.xml
Normal file
9
app/src/main/res/drawable/ic_meta_date.xml
Normal file
|
@ -0,0 +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>
|
9
app/src/main/res/drawable/ic_meta_encryption.xml
Normal file
9
app/src/main/res/drawable/ic_meta_encryption.xml
Normal file
|
@ -0,0 +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>
|
10
app/src/main/res/drawable/ic_meta_error.xml
Normal file
10
app/src/main/res/drawable/ic_meta_error.xml
Normal file
|
@ -0,0 +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>
|
9
app/src/main/res/drawable/ic_meta_flavour.xml
Normal file
9
app/src/main/res/drawable/ic_meta_flavour.xml
Normal file
|
@ -0,0 +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>
|
9
app/src/main/res/drawable/ic_meta_format.xml
Normal file
9
app/src/main/res/drawable/ic_meta_format.xml
Normal file
|
@ -0,0 +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>
|
9
app/src/main/res/drawable/ic_meta_model.xml
Normal file
9
app/src/main/res/drawable/ic_meta_model.xml
Normal file
|
@ -0,0 +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>
|
9
app/src/main/res/drawable/ic_meta_name.xml
Normal file
9
app/src/main/res/drawable/ic_meta_name.xml
Normal file
|
@ -0,0 +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>
|
10
app/src/main/res/drawable/ic_meta_ok.xml
Normal file
10
app/src/main/res/drawable/ic_meta_ok.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="@color/toastOk"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_meta_version.xml
Normal file
9
app/src/main/res/drawable/ic_meta_version.xml
Normal file
|
@ -0,0 +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>
|
10
app/src/main/res/drawable/ic_meta_warning.xml
Normal file
10
app/src/main/res/drawable/ic_meta_warning.xml
Normal file
|
@ -0,0 +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>
|
10
app/src/main/res/drawable/ic_toast_check.xml
Normal file
10
app/src/main/res/drawable/ic_toast_check.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/toastOk"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M10,17L5,12L6.41,10.58L10,14.17L17.59,6.58L19,8M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_toast_delete_confirm.xml
Normal file
10
app/src/main/res/drawable/ic_toast_delete_confirm.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/toastInfo"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19M8.46,11.88L9.87,10.47L12,12.59L14.12,10.47L15.53,11.88L13.41,14L15.53,16.12L14.12,17.53L12,15.41L9.88,17.53L8.47,16.12L10.59,14L8.46,11.88M15.5,4L14.5,3H9.5L8.5,4H5V6H19V4H15.5Z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_toast_error.xml
Normal file
10
app/src/main/res/drawable/ic_toast_error.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/toastError"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M13,13H11V7H13M13,17H11V15H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_toast_info.xml
Normal file
10
app/src/main/res/drawable/ic_toast_info.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/toastInfo"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M13,9H11V7H13M13,17H11V11H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_toast_warn.xml
Normal file
10
app/src/main/res/drawable/ic_toast_warn.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/toastWarn"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M13,13H11V7H13M13,17H11V15H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
|
||||
</vector>
|
12
app/src/main/res/drawable/toast_border_ok.xml
Normal file
12
app/src/main/res/drawable/toast_border_ok.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:shape="rectangle"
|
||||
tools:ignore="PrivateResource">
|
||||
<corners android:radius="18dp" />
|
||||
<solid android:color="@color/background_floating_material_dark" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/toastBorder" />
|
||||
</shape>
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Loading this view directly, without proper Theme, will likely result in crash due to lack of ?dialog... attribute definitions
|
||||
Please use AlertDialogHelper or wrap inflater context with ContextThemeWrapper(context, R.style.AppTheme)
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -8,14 +12,15 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="@color/dialog_title_background"
|
||||
android:background="?dialogTitleBackground"
|
||||
android:orientation="horizontal"
|
||||
android:padding="5dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/alertdialog_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
android:tint="?dialogTitleIconTint" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/alertdialog_title"
|
||||
|
@ -23,10 +28,13 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="50dp"
|
||||
android:layout_toEndOf="@id/alertdialog_icon"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textColor="?dialogTitleColor" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
@ -36,5 +44,4 @@
|
|||
android:orientation="horizontal"
|
||||
android:padding="5dp" />
|
||||
|
||||
|
||||
</LinearLayout>
|
30
app/src/main/res/layout/dialog_alert_import_summary.xml
Normal file
30
app/src/main/res/layout/dialog_alert_import_summary.xml
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<TableLayout
|
||||
android:id="@+id/summary_table"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:stretchColumns="2" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/summary_details_btn"
|
||||
style="?android:attr/buttonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="3dp"
|
||||
android:text="@string/check_preferences_details_btn"
|
||||
android:textColor="@color/colorTreatmentButton"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
19
app/src/main/res/layout/dialog_alert_two_messages.xml
Normal file
19
app/src/main/res/layout/dialog_alert_two_messages.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/password_prompt_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:text="@string/password_preferences_decrypt_prompt"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@color/colorAccent" />
|
||||
|
||||
</LinearLayout>
|
14
app/src/main/res/layout/import_summary_details.xml
Normal file
14
app/src/main/res/layout/import_summary_details.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/details_webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="400dp"
|
||||
android:background="@android:color/transparent" />
|
||||
</LinearLayout>
|
36
app/src/main/res/layout/import_summary_item.xml
Normal file
36
app/src/main/res/layout/import_summary_item.xml
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/status_icon"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_margin="4dp"
|
||||
android:src="@drawable/ic_toast_check" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/summary_icon"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginRight="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:src="@drawable/ic_meta_format"
|
||||
android:tint="#ffffff" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_weight="1"
|
||||
android:text="Sample text here"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</TableRow>
|
||||
|
32
app/src/main/res/layout/toast.xml
Normal file
32
app/src/main/res/layout/toast.xml
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/toast_border_ok">
|
||||
|
||||
<ImageView
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="6dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:src="@drawable/ic_toast_check"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="6dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginRight="18dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="Toast goes here..."
|
||||
android:textColor="@color/toastBase"
|
||||
android:textSize="18sp"
|
||||
tools:ignore="HardcodedText,RtlHardcoded" />
|
||||
</LinearLayout>
|
|
@ -35,6 +35,7 @@
|
|||
<string name="objectives_useloop">Отворете съдържанието на Цикъл плъгина.</string>
|
||||
<string name="objectives_usescale">Използвайте функцията за удължаване на периода на графиката, като задържите върху нея.</string>
|
||||
<string name="objectives_button_enter">Въведи</string>
|
||||
<string name="enter_code_obtained_from_developers_to_bypass_the_rest_of_objectives">Ако имате поне 3 месеца опит с други системи, които биха може да получите код за пропускане на целите. Прочетете https://androidaps.readthedocs.io/en/latest/EN/Usage/Objectives.html#skip-objectives за получаване на подробна информация.</string>
|
||||
<string name="codeaccepted">Код приет!</string>
|
||||
<string name="codeinvalid">Неправилен код</string>
|
||||
<string name="objectives_exam_objective">Докажете знанията си</string>
|
||||
|
@ -45,6 +46,7 @@
|
|||
<string name="requestcode">Код (request code): %1$s</string>
|
||||
<string name="objectives_hint">(отбележете всички правилни отговори)</string>
|
||||
<string name="disconnectpump_hint" formatted="false">https://androidaps.readthedocs.io/en/latest/EN/Getting-Started/FAQ.html#what-to-do-when-taking-a-shower-or-bath</string>
|
||||
<string name="notconnected">Няма връзка с Интернет!</string>
|
||||
<string name="failedretrievetime">Не може да се вземе времето</string>
|
||||
<string name="requirementnotmet">Задачите не са изпълнени</string>
|
||||
<plurals name="objective_days">
|
||||
|
|
|
@ -564,7 +564,7 @@
|
|||
<string name="virtualpump_firmware_label">Firmware</string>
|
||||
<string name="pump_lastconnection_label">Última ligação</string>
|
||||
<string name="danar_bluetooth_status">Estado Bluetooth</string>
|
||||
<string name="nav_about">Acerca</string>
|
||||
<string name="nav_about">Sobre</string>
|
||||
<string name="smscommunicator_missingsmspermission">Falta de permissão SMS</string>
|
||||
<string name="smscommunicator_missingphonestatepermission">Falta permissão do estado do telefone</string>
|
||||
<string name="xdripstatus_settings">Estado xDrip (relógio)</string>
|
||||
|
@ -742,7 +742,7 @@
|
|||
<string name="basalprofilenotaligned">Valores das basais não definidos por horas: %1$s</string>
|
||||
<string name="zerovalueinprofile">Perfil inválido: %1$s</string>
|
||||
<string name="combo_programming_bolus">A programar a bomba para injectar o bolus</string>
|
||||
<string name="combo_refresh">Actualizar</string>
|
||||
<string name="combo_refresh">Atualizar</string>
|
||||
<string name="combo_pump_state_label">Estado</string>
|
||||
<string name="combo_pump_activity_label">Atividades</string>
|
||||
<string name="combo_no_pump_connection">Sem conexão há %1$d min</string>
|
||||
|
@ -841,7 +841,7 @@
|
|||
<string name="combo_error_updating_treatment_record">O bolus foi injectado, mas não foi possível adicionar ao histórico. Isto é possível se dois bolus do mesmo valor baixo foram administrados nos últimos dois minutos. Por favor verifique o histórico da bomba e use o Careportal para adicionar os dados em falta caso necessário. Cuidado para não adicionar dados com o mesmo valor no mesmo minuto.</string>
|
||||
<string name="combo_high_temp_rejected_due_to_pump_history_changes">A rejeitar a temporal alta uma vez que o cálculo não considerou as mudanças do histórico da bomba</string>
|
||||
<string name="combo_activity_checking_pump_state">Atualizando estado da bomba</string>
|
||||
<string name="combo_warning_pump_basal_rate_changed">O rácio da basal foi alterado na bomba e vai ser actualizado em breve</string>
|
||||
<string name="combo_warning_pump_basal_rate_changed">A taxa basal na bomba mudou e será atualizada em breve</string>
|
||||
<string name="combo_error_failure_reading_changed_basal_rate">Rácio da basal foi alterado na bomba, mas a leitura falhou</string>
|
||||
<string name="combo_activity_checking_for_history_changes">Verificando alterações de histórico</string>
|
||||
<string name="combo_error_multiple_boluses_with_identical_timestamp">Bólus múltiplos, com a mesma quantidade e no mesmo minuto acabaram de ser importados. Apenas um registo pôde ser adicionado aos tratamentos. Por favor confirma na bomba e adicione o registo de bólus manualmente, utilizando o separador do Careportal. Tenha em atenção que deve criar um bólus com um tempo em que nã oexistem outros registos de bólus.</string>
|
||||
|
|
7
app/src/main/res/values/attrs.xml
Normal file
7
app/src/main/res/values/attrs.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<resources>
|
||||
|
||||
<attr name="dialogTitleBackground" format="reference" />
|
||||
<attr name="dialogTitleColor" format="reference" />
|
||||
<attr name="dialogTitleIconTint" format="reference" />
|
||||
|
||||
</resources>
|
|
@ -47,6 +47,8 @@
|
|||
|
||||
<color name="dialog_title_background">#303030</color>
|
||||
<color name="activity_title_background">#121212</color>
|
||||
<color name="dialog_title_color">#FFFFFF</color>
|
||||
<color name="dialog_title_icon_tint">#FFFFFF</color>
|
||||
|
||||
<color name="cardColorBackground">#121212</color>
|
||||
<color name="cardObjectiveText">#779ECB</color>
|
||||
|
@ -87,4 +89,21 @@
|
|||
<color name="ribbonTextCritical">#FFFFFF</color>
|
||||
|
||||
<color name="splashBackground">#2E2E2E</color>
|
||||
|
||||
<color name="warningAlertBackground">#FFFB8C00</color>
|
||||
<color name="warningAlertHeaderText">#FF000000</color>
|
||||
|
||||
<color name="errorAlertBackground">#FFFF5555</color>
|
||||
<color name="errorAlertHeaderText">#FF000000</color>
|
||||
|
||||
<color name="toastBorder">#666666</color>
|
||||
<color name="toastBase">#ffffff</color>
|
||||
<color name="toastOk">#77dd77</color>
|
||||
<color name="toastError">#ff0400</color>
|
||||
<color name="toastWarn">#FF8C00</color>
|
||||
<color name="toastInfo">#03A9F4</color>
|
||||
|
||||
<color name="metadataTextWarning">#FF8C00</color>
|
||||
<color name="metadataTextError">#FF5555</color>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<string name="custom_password">Custom password</string>
|
||||
<string name="noprotection">No protection</string>
|
||||
<string name="protection">Protection</string>
|
||||
<string name="master_password_missing">Master password is not set!\n\nPlease set your Master password in Preferences (%1$s → %2$s)</string>
|
||||
|
||||
<string name="password_set">Password set!</string>
|
||||
<string name="password_not_set">Password not set</string>
|
||||
|
|
|
@ -253,6 +253,51 @@
|
|||
<string name="dismiss">DISMISS</string>
|
||||
<string name="language" translatable="false">Language</string>
|
||||
|
||||
<string name="password_preferences_encrypt_prompt">You will be asked for master password, which will be used to encrypt exported preferences.</string>
|
||||
<string name="password_preferences_decrypt_prompt">You will be asked for master password, which is needed to decrypt imported preferences.</string>
|
||||
<string name="preferences_export_canceled">Export canceled! Preferences were NOT exported!</string>
|
||||
<string name="preferences_import_canceled">Import canceled! Preferences were NOT imported!</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>
|
||||
<string name="check_preferences_details_btn">Explain import issues…</string>
|
||||
<string name="check_preferences_details_title">Import issues details</string>
|
||||
<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_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 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="danarpump">DanaR</string>
|
||||
<string name="connecting">Connecting</string>
|
||||
<string name="connected">Connected</string>
|
||||
|
@ -1147,6 +1192,8 @@
|
|||
<string name="error_adding_treatment_title">Treatment data incomplete</string>
|
||||
<string name="maintenance_settings">Maintenance Settings</string>
|
||||
<string name="maintenance_email">Email recipient</string>
|
||||
<string name="key_maintenance_encrypt_exported_prefs">maintenance_encrypt_exported_prefs</string>
|
||||
<string name="maintenance_encrypt_exported_prefs">Encrypt exported settings</string>
|
||||
<string name="key_maintenance_logs_email" translatable="false">maintenance_logs_email</string>
|
||||
<string name="key_maintenance_logs_amount" translatable="false">maintenance_logs_amount</string>
|
||||
<string name="key_logshipper_amount" translatable="false">logshipper_amount</string>
|
||||
|
@ -1387,6 +1434,8 @@
|
|||
<string name="exists">exists</string>
|
||||
<string name="notexists">not exists</string>
|
||||
<string name="temptargetcompared">Temp target %1$s</string>
|
||||
<string name="btdevicecompared">Bluetooth connection to device %1$s %2$s</string>
|
||||
<string name="btdevice">Connection to Bluetooth device </string>
|
||||
<string name="wifissidcompared">WiFi SSID %1$s %2$s</string>
|
||||
<string name="autosenscompared">Autosens %1$s %2$s %%</string>
|
||||
<string name="autosenslabel">Autosens %</string>
|
||||
|
@ -1748,4 +1797,6 @@
|
|||
<string name="num2pin">2: (8 digits)</string>
|
||||
<string name="resetpairing">Reset pairing information?</string>
|
||||
<string name="invalidpairing">Invalid pairing information. Requesting new pairing</string>
|
||||
<string name="onconnect">On connect</string>
|
||||
<string name="ondisconnect">On disconnect</string>
|
||||
</resources>
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="dialogTitleBackground">@color/dialog_title_background</item>
|
||||
<item name="dialogTitleColor">@color/dialog_title_color</item>
|
||||
<item name="dialogTitleIconTint">@color/dialog_title_icon_tint</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.NoActionBar">
|
||||
|
@ -67,4 +70,28 @@
|
|||
<item name="android:textColor">#ff0000</item>
|
||||
</style>
|
||||
|
||||
<style name="AppThemeWarningDialog" parent="AppTheme">
|
||||
<item name="alertDialogTheme">@style/AppThemeWarningDialogTheme</item>
|
||||
<item name="dialogTitleBackground">@color/warningAlertBackground</item>
|
||||
<item name="dialogTitleColor">@color/warningAlertHeaderText</item>
|
||||
<item name="dialogTitleIconTint">@color/warningAlertHeaderText</item>
|
||||
</style>
|
||||
|
||||
<style name="AppThemeWarningDialogTheme" parent="Theme.AppCompat.Dialog.MinWidth">
|
||||
<item name="android:windowBackground">@drawable/alert_border_warning</item>
|
||||
<item name="colorAccent">@color/warningAlertBackground</item>
|
||||
</style>
|
||||
|
||||
<style name="AppThemeErrorDialog" parent="AppTheme">
|
||||
<item name="alertDialogTheme">@style/AppThemeErrorDialogTheme</item>
|
||||
<item name="dialogTitleBackground">@color/errorAlertBackground</item>
|
||||
<item name="dialogTitleColor">@color/errorAlertHeaderText</item>
|
||||
<item name="dialogTitleIconTint">@color/errorAlertHeaderText</item>
|
||||
</style>
|
||||
|
||||
<style name="AppThemeErrorDialogTheme" parent="Theme.AppCompat.Dialog.MinWidth">
|
||||
<item name="android:windowBackground">@drawable/alert_border_error</item>
|
||||
<item name="colorAccent">@color/errorAlertBackground</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -26,6 +26,10 @@
|
|||
validate:minNumber="1"
|
||||
validate:testType="numericRange"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="@string/key_maintenance_encrypt_exported_prefs"
|
||||
android:title="@string/maintenance_encrypt_exported_prefs" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package info.nightscout.androidaps.plugins.general.automation.elements
|
||||
|
||||
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerTestBase
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.powermock.modules.junit4.PowerMockRunner
|
||||
|
||||
@RunWith(PowerMockRunner::class)
|
||||
class ComparatorConnectTest : TriggerTestBase() {
|
||||
|
||||
@Test fun labelsTest() {
|
||||
Assert.assertEquals(2, ComparatorConnect.Compare.labels(resourceHelper).size)
|
||||
}
|
||||
|
||||
@Test fun setValueTest() {
|
||||
val c = ComparatorConnect(injector)
|
||||
c.value = ComparatorConnect.Compare.ON_DISCONNECT
|
||||
Assert.assertEquals(ComparatorConnect.Compare.ON_DISCONNECT, c.value)
|
||||
}
|
||||
}
|
|
@ -1,12 +1,14 @@
|
|||
package info.nightscout.androidaps.plugins.general.automation.elements
|
||||
|
||||
import info.nightscout.androidaps.Constants
|
||||
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
|
||||
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerTestBase
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest
|
||||
import org.powermock.modules.junit4.PowerMockRunner
|
||||
|
||||
@RunWith(PowerMockRunner::class)
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package info.nightscout.androidaps.plugins.general.automation.triggers
|
||||
|
||||
import com.google.common.base.Optional
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.plugins.general.automation.elements.ComparatorConnect
|
||||
import org.json.JSONObject
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.powermock.modules.junit4.PowerMockRunner
|
||||
|
||||
@RunWith(PowerMockRunner::class)
|
||||
class TriggerBTDeviceTest : TriggerTestBase() {
|
||||
|
||||
var now = 1514766900000L
|
||||
var someName = "Headset"
|
||||
var btJson = "{\"data\":{\"comparator\":\"ON_CONNECT\",\"name\":\"Headset\"},\"type\":\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerBTDevice\"}"
|
||||
|
||||
@Test fun shouldRun() {
|
||||
val t: TriggerBTDevice = TriggerBTDevice(injector)
|
||||
}
|
||||
|
||||
@Test fun toJSON() {
|
||||
val t: TriggerBTDevice = TriggerBTDevice(injector)
|
||||
t.btDevice.value = someName
|
||||
Assert.assertEquals(btJson, t.toJSON())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fromJSON() {
|
||||
val t2 = TriggerDummy(injector).instantiate(JSONObject(btJson)) as TriggerBTDevice
|
||||
Assert.assertEquals(ComparatorConnect.Compare.ON_CONNECT, t2.comparator.value)
|
||||
Assert.assertEquals("Headset", t2.btDevice.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun icon() {
|
||||
Assert.assertEquals(Optional.of(R.drawable.ic_bluetooth_white_48dp), TriggerBTDevice(injector).icon())
|
||||
}
|
||||
|
||||
@Test fun duplicate() {
|
||||
val t: TriggerBTDevice = TriggerBTDevice(injector).also {
|
||||
it.comparator.value = ComparatorConnect.Compare.ON_DISCONNECT
|
||||
it.btDevice.value = someName
|
||||
}
|
||||
val t1 = t.duplicate() as TriggerBTDevice
|
||||
Assert.assertEquals("Headset", t1.btDevice.value)
|
||||
Assert.assertEquals(ComparatorConnect.Compare.ON_DISCONNECT, t.comparator.value)
|
||||
}
|
||||
}
|
|
@ -3,27 +3,23 @@ package info.nightscout.androidaps.plugins.general.automation.triggers
|
|||
import android.content.Context
|
||||
import dagger.android.AndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.androidaps.TestBase
|
||||
import info.nightscout.androidaps.TestBaseWithProfile
|
||||
import info.nightscout.androidaps.events.EventNetworkChange
|
||||
import info.nightscout.androidaps.interfaces.ActivePluginProvider
|
||||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
||||
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction
|
||||
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
|
||||
import info.nightscout.androidaps.plugins.general.automation.elements.InputBg
|
||||
import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
|
||||
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
|
||||
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
|
||||
import info.nightscout.androidaps.receivers.ReceiverStatusStore
|
||||
import info.nightscout.androidaps.services.LastLocationDataContainer
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||
import org.junit.Before
|
||||
import org.mockito.Mock
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest
|
||||
|
||||
@PrepareForTest(LastLocationDataContainer::class)
|
||||
@PrepareForTest(LastLocationDataContainer::class, AutomationPlugin::class)
|
||||
open class TriggerTestBase : TestBaseWithProfile() {
|
||||
|
||||
@Mock lateinit var profileFunction: ProfileFunction
|
||||
|
@ -32,6 +28,7 @@ open class TriggerTestBase : TestBaseWithProfile() {
|
|||
@Mock lateinit var activePlugin: ActivePluginProvider
|
||||
@Mock lateinit var iobCobCalculatorPlugin: IobCobCalculatorPlugin
|
||||
@Mock lateinit var context: Context
|
||||
@Mock lateinit var automationPlugin: AutomationPlugin
|
||||
|
||||
lateinit var receiverStatusStore: ReceiverStatusStore
|
||||
|
||||
|
@ -56,6 +53,10 @@ open class TriggerTestBase : TestBaseWithProfile() {
|
|||
if (it is TriggerBg) {
|
||||
it.profileFunction = profileFunction
|
||||
}
|
||||
if (it is TriggerBTDevice) {
|
||||
it.context = context
|
||||
it.automationPlugin = automationPlugin
|
||||
}
|
||||
if (it is TriggerWifiSsid) {
|
||||
it.receiverStatusStore = receiverStatusStore
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package info.nightscout.androidaps.plugins.general.maintenance
|
||||
|
||||
import info.nightscout.androidaps.MainApp
|
||||
import info.nightscout.androidaps.TestBase
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.ClassicPrefsFormat
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.Prefs
|
||||
import info.nightscout.androidaps.testing.mockers.AAPSMocker
|
||||
import info.nightscout.androidaps.testing.utils.SingleStringStorage
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest
|
||||
import org.powermock.modules.junit4.PowerMockRunner
|
||||
import java.io.File
|
||||
|
||||
@RunWith(PowerMockRunner::class)
|
||||
@PrepareForTest(AAPSMocker::class, MainApp::class, File::class)
|
||||
|
||||
class ClassicPrefsFormatTest : TestBase() {
|
||||
|
||||
@Mock lateinit var resourceHelper: ResourceHelper
|
||||
@Mock lateinit var sp: SP
|
||||
|
||||
@Before
|
||||
fun mock() {
|
||||
AAPSMocker.prepareMock()
|
||||
AAPSMocker.resetMockedSharedPrefs()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preferenceLoadingTest() {
|
||||
val test = "key1::val1\nkeyB::valB"
|
||||
|
||||
val classicFormat = ClassicPrefsFormat(resourceHelper, SingleStringStorage(test))
|
||||
val prefs = classicFormat.loadPreferences(AAPSMocker.getMockedFile(), "")
|
||||
|
||||
Assert.assertThat(prefs.values.size, CoreMatchers.`is`(2))
|
||||
Assert.assertThat(prefs.values["key1"], CoreMatchers.`is`("val1"))
|
||||
Assert.assertThat(prefs.values["keyB"], CoreMatchers.`is`("valB"))
|
||||
Assert.assertNull(prefs.values["key3"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preferenceSavingTest() {
|
||||
val storage = SingleStringStorage("")
|
||||
val classicFormat = ClassicPrefsFormat(resourceHelper, storage)
|
||||
val prefs = Prefs(
|
||||
mapOf(
|
||||
"key1" to "A",
|
||||
"keyB" to "2"
|
||||
),
|
||||
mapOf()
|
||||
)
|
||||
|
||||
classicFormat.savePreferences(AAPSMocker.getMockedFile(), prefs)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
package info.nightscout.androidaps.plugins.general.maintenance
|
||||
|
||||
import info.nightscout.androidaps.MainApp
|
||||
import info.nightscout.androidaps.TestBase
|
||||
import info.nightscout.androidaps.plugins.general.maintenance.formats.*
|
||||
import info.nightscout.androidaps.testing.mockers.AAPSMocker
|
||||
import info.nightscout.androidaps.testing.utils.SingleStringStorage
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.json.JSONException
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.powermock.core.classloader.annotations.PowerMockIgnore
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest
|
||||
import org.powermock.modules.junit4.PowerMockRunner
|
||||
import java.io.File
|
||||
|
||||
@PowerMockIgnore("javax.crypto.*")
|
||||
@RunWith(PowerMockRunner::class)
|
||||
@PrepareForTest(AAPSMocker::class, MainApp::class, File::class, ResourceHelper::class)
|
||||
|
||||
class EncryptedPrefsFormatTest : TestBase() {
|
||||
|
||||
@Mock lateinit var resourceHelper: ResourceHelper
|
||||
@Mock lateinit var sp: SP
|
||||
|
||||
@Before
|
||||
fun mock() {
|
||||
AAPSMocker.prepareMock()
|
||||
AAPSMocker.resetMockedSharedPrefs()
|
||||
Mockito.`when`(resourceHelper.gs(ArgumentMatchers.anyInt())).thenReturn("mock translation")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preferenceLoadingTest() {
|
||||
val frozenPrefs = "{\n" +
|
||||
" \"metadata\": {},\n" +
|
||||
" \"security\": {\n" +
|
||||
" \"salt\": \"9581d7a9e56d8127ad6b74a876fa60b192b1c6f4343d857bc07e3874589f2fc9\",\n" +
|
||||
" \"file_hash\": \"9122fd04a4938030b62f6b9d6dda63a11c265e673c4aecbcb6dcd62327c025bb\",\n" +
|
||||
" \"content_hash\": \"23f999f6e6d325f649b61871fe046a94e110bf1587ff070fb66a0f8085b2760c\",\n" +
|
||||
" \"algorithm\": \"v1\"\n" +
|
||||
" },\n" +
|
||||
" \"format\": \"aaps_encrypted\",\n" +
|
||||
" \"content\": \"DJ5+HP/gq7icRQhbG9PEBJCMuNwBssIytfEQPCNkzn7PHMfMZuc09vYQg3qzFkmULLiotg==\"\n" +
|
||||
"}"
|
||||
|
||||
val storage = SingleStringStorage(frozenPrefs)
|
||||
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
|
||||
val prefs = encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "sikret")
|
||||
|
||||
Assert.assertThat(prefs.values.size, CoreMatchers.`is`(2))
|
||||
Assert.assertThat(prefs.values["key1"], CoreMatchers.`is`("A"))
|
||||
Assert.assertThat(prefs.values["keyB"], CoreMatchers.`is`("2"))
|
||||
|
||||
Assert.assertThat(prefs.metadata[PrefsMetadataKey.FILE_FORMAT]?.status, CoreMatchers.`is`(PrefsStatus.OK))
|
||||
Assert.assertThat(prefs.metadata[PrefsMetadataKey.FILE_FORMAT]?.value, CoreMatchers.`is`(EncryptedPrefsFormat.FORMAT_KEY_ENC))
|
||||
Assert.assertThat(prefs.metadata[PrefsMetadataKey.ENCRYPTION]?.status, CoreMatchers.`is`(PrefsStatus.OK))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preferenceSavingTest() {
|
||||
val storage = SingleStringStorage("")
|
||||
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
|
||||
val prefs = Prefs(
|
||||
mapOf(
|
||||
"key1" to "A",
|
||||
"keyB" to "2"
|
||||
),
|
||||
mapOf(
|
||||
PrefsMetadataKey.ENCRYPTION to PrefMetadata(EncryptedPrefsFormat.FORMAT_KEY_ENC, PrefsStatus.OK)
|
||||
)
|
||||
)
|
||||
encryptedFormat.savePreferences(AAPSMocker.getMockedFile(), prefs, "sikret")
|
||||
aapsLogger.debug(storage.contents)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun importExportStabilityTest() {
|
||||
val storage = SingleStringStorage("")
|
||||
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
|
||||
val prefsIn = Prefs(
|
||||
mapOf(
|
||||
"testpref1" to "--1--",
|
||||
"testpref2" to "another"
|
||||
),
|
||||
mapOf(
|
||||
PrefsMetadataKey.ENCRYPTION to PrefMetadata(EncryptedPrefsFormat.FORMAT_KEY_ENC, PrefsStatus.OK)
|
||||
)
|
||||
)
|
||||
encryptedFormat.savePreferences(AAPSMocker.getMockedFile(), prefsIn, "tajemnica")
|
||||
val prefsOut = encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "tajemnica")
|
||||
|
||||
Assert.assertThat(prefsOut.values.size, CoreMatchers.`is`(2))
|
||||
Assert.assertThat(prefsOut.values["testpref1"], CoreMatchers.`is`("--1--"))
|
||||
Assert.assertThat(prefsOut.values["testpref2"], CoreMatchers.`is`("another"))
|
||||
|
||||
Assert.assertThat(prefsOut.metadata[PrefsMetadataKey.FILE_FORMAT]?.status, CoreMatchers.`is`(PrefsStatus.OK))
|
||||
Assert.assertThat(prefsOut.metadata[PrefsMetadataKey.FILE_FORMAT]?.value, CoreMatchers.`is`(EncryptedPrefsFormat.FORMAT_KEY_ENC))
|
||||
Assert.assertThat(prefsOut.metadata[PrefsMetadataKey.ENCRYPTION]?.status, CoreMatchers.`is`(PrefsStatus.OK))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wrongPasswordTest() {
|
||||
val frozenPrefs = "{\n" +
|
||||
" \"metadata\": {},\n" +
|
||||
" \"security\": {\n" +
|
||||
" \"salt\": \"9581d7a9e56d8127ad6b74a876fa60b192b1c6f4343d857bc07e3874589f2fc9\",\n" +
|
||||
" \"file_hash\": \"9122fd04a4938030b62f6b9d6dda63a11c265e673c4aecbcb6dcd62327c025bb\",\n" +
|
||||
" \"content_hash\": \"23f999f6e6d325f649b61871fe046a94e110bf1587ff070fb66a0f8085b2760c\",\n" +
|
||||
" \"algorithm\": \"v1\"\n" +
|
||||
" },\n" +
|
||||
" \"format\": \"aaps_encrypted\",\n" +
|
||||
" \"content\": \"DJ5+HP/gq7icRQhbG9PEBJCMuNwBssIytfEQPCNkzn7PHMfMZuc09vYQg3qzFkmULLiotg==\"\n" +
|
||||
"}"
|
||||
|
||||
val storage = SingleStringStorage(frozenPrefs)
|
||||
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
|
||||
val prefs = encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "it-is-NOT-right-secret")
|
||||
|
||||
Assert.assertThat(prefs.values.size, CoreMatchers.`is`(0))
|
||||
|
||||
Assert.assertThat(prefs.metadata[PrefsMetadataKey.FILE_FORMAT]?.status, CoreMatchers.`is`(PrefsStatus.OK))
|
||||
Assert.assertThat(prefs.metadata[PrefsMetadataKey.FILE_FORMAT]?.value, CoreMatchers.`is`(EncryptedPrefsFormat.FORMAT_KEY_ENC))
|
||||
Assert.assertThat(prefs.metadata[PrefsMetadataKey.ENCRYPTION]?.status, CoreMatchers.`is`(PrefsStatus.ERROR))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tamperedMetadataTest() {
|
||||
val frozenPrefs = "{\n" +
|
||||
" \"metadata\": {" +
|
||||
" \"created-by\":\"I am legit, trust me, no-one lies on internets!\"" +
|
||||
" },\n" +
|
||||
" \"security\": {\n" +
|
||||
" \"salt\": \"9581d7a9e56d8127ad6b74a876fa60b192b1c6f4343d857bc07e3874589f2fc9\",\n" +
|
||||
" \"file_hash\": \"9122fd04a4938030b62f6b9d6dda63a11c265e673c4aecbcb6dcd62327c025bb\",\n" +
|
||||
" \"content_hash\": \"23f999f6e6d325f649b61871fe046a94e110bf1587ff070fb66a0f8085b2760c\",\n" +
|
||||
" \"algorithm\": \"v1\"\n" +
|
||||
" },\n" +
|
||||
" \"format\": \"aaps_encrypted\",\n" +
|
||||
" \"content\": \"DJ5+HP/gq7icRQhbG9PEBJCMuNwBssIytfEQPCNkzn7PHMfMZuc09vYQg3qzFkmULLiotg==\"\n" +
|
||||
"}"
|
||||
|
||||
val storage = SingleStringStorage(frozenPrefs)
|
||||
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
|
||||
val prefs = encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "sikret")
|
||||
|
||||
// contents were not tampered and we can decrypt them
|
||||
Assert.assertThat(prefs.values.size, CoreMatchers.`is`(2))
|
||||
|
||||
// but checksum fails on metadata, so overall security fails
|
||||
Assert.assertThat(prefs.metadata[PrefsMetadataKey.ENCRYPTION]?.status, CoreMatchers.`is`(PrefsStatus.ERROR))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tamperedContentsTest() {
|
||||
val frozenPrefs = "{\n" +
|
||||
" \"metadata\": {},\n" +
|
||||
" \"security\": {\n" +
|
||||
" \"salt\": \"9581d7a9e56d8127ad6b74a876fa60b192b1c6f4343d857bc07e3874589f2fc9\",\n" +
|
||||
" \"file_hash\": \"9122fd04a4938030b62f6b9d6dda63a11c265e673c4aecbcb6dcd62327c025bb\",\n" +
|
||||
" \"content_hash\": \"23f999f6e6d325f649b61871fe046a94e110bf1587ff070fb66a0f8085b2760a\",\n" +
|
||||
" \"algorithm\": \"v1\"\n" +
|
||||
" },\n" +
|
||||
" \"format\": \"aaps_encrypted\",\n" +
|
||||
" \"content\": \"DJ5+HP/gq7icRQhbG9PEBJCMuNwBssIytfEQPCNkzn7PHMfMZuc09vYQg3qzFkmULLiotg==\"\n" +
|
||||
"}"
|
||||
|
||||
val storage = SingleStringStorage(frozenPrefs)
|
||||
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
|
||||
val prefs = encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "sikret")
|
||||
|
||||
Assert.assertThat(prefs.values.size, CoreMatchers.`is`(0))
|
||||
Assert.assertThat(prefs.metadata[PrefsMetadataKey.ENCRYPTION]?.status, CoreMatchers.`is`(PrefsStatus.ERROR))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingFieldsTest() {
|
||||
val frozenPrefs = "{\n" +
|
||||
" \"format\": \"aaps_encrypted\",\n" +
|
||||
" \"content\": \"lets get rid of metadata and security!\"\n" +
|
||||
"}"
|
||||
|
||||
val storage = SingleStringStorage(frozenPrefs)
|
||||
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
|
||||
val prefs = encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "sikret")
|
||||
|
||||
Assert.assertThat(prefs.values.size, CoreMatchers.`is`(0))
|
||||
Assert.assertThat(prefs.metadata[PrefsMetadataKey.FILE_FORMAT]?.status, CoreMatchers.`is`(PrefsStatus.ERROR))
|
||||
}
|
||||
|
||||
@Test(expected = PrefFormatError::class)
|
||||
fun garbageInputTest() {
|
||||
val frozenPrefs = "whatever man, i duno care"
|
||||
|
||||
val storage = SingleStringStorage(frozenPrefs)
|
||||
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
|
||||
encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "sikret")
|
||||
}
|
||||
|
||||
@Test(expected = PrefFormatError::class)
|
||||
fun unknownFormatTest() {
|
||||
val frozenPrefs = "{\n" +
|
||||
" \"metadata\": {},\n" +
|
||||
" \"security\": {\n" +
|
||||
" \"salt\": \"9581d7a9e56d8127ad6b74a876fa60b192b1c6f4343d857bc07e3874589f2fc9\",\n" +
|
||||
" \"file_hash\": \"9122fd04a4938030b62f6b9d6dda63a11c265e673c4aecbcb6dcd62327c025bb\",\n" +
|
||||
" \"content_hash\": \"23f999f6e6d325f649b61871fe046a94e110bf1587ff070fb66a0f8085b2760c\",\n" +
|
||||
" \"algorithm\": \"v1\"\n" +
|
||||
" },\n" +
|
||||
" \"format\": \"aaps_9000_new_format\",\n" +
|
||||
" \"content\": \"DJ5+HP/gq7icRQhbG9PEBJCMuNwBssIytfEQPCNkzn7PHMfMZuc09vYQg3qzFkmULLiotg==\"\n" +
|
||||
"}"
|
||||
|
||||
val storage = SingleStringStorage(frozenPrefs)
|
||||
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
|
||||
encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "sikret")
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package info.nightscout.androidaps.testing.mockers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.powermock.api.mockito.PowerMockito;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.testing.mocks.SharedPreferencesMock;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.powermock.api.mockito.PowerMockito.mockStatic;
|
||||
|
||||
public class AAPSMocker {
|
||||
|
||||
private static final Map<String, SharedPreferences> mockedSharedPrefs = new HashMap<>();
|
||||
|
||||
public static void prepareMock() throws Exception {
|
||||
Context mockedContext = mock(Context.class);
|
||||
mockStatic(MainApp.class, InvocationOnMock::callRealMethod);
|
||||
|
||||
PowerMockito.when(mockedContext, "getSharedPreferences", ArgumentMatchers.anyString(), ArgumentMatchers.anyInt()).thenAnswer(invocation -> {
|
||||
|
||||
final String key = invocation.getArgument(0);
|
||||
if (mockedSharedPrefs.containsKey(key)) {
|
||||
return mockedSharedPrefs.get(key);
|
||||
} else {
|
||||
SharedPreferencesMock newPrefs = new SharedPreferencesMock();
|
||||
mockedSharedPrefs.put(key, newPrefs);
|
||||
return newPrefs;
|
||||
}
|
||||
});
|
||||
|
||||
resetMockedSharedPrefs();
|
||||
}
|
||||
|
||||
public static void resetMockedSharedPrefs() {
|
||||
mockedSharedPrefs.clear();
|
||||
}
|
||||
|
||||
public static File getMockedFile() {
|
||||
File file = mock(File.class);
|
||||
when(file.exists()).thenReturn(true);
|
||||
when(file.canRead()).thenReturn(true);
|
||||
when(file.canWrite()).thenReturn(true);
|
||||
return file;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package info.nightscout.androidaps.testing.mocks;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class SharedPreferencesMock implements SharedPreferences {
|
||||
|
||||
private final EditorInternals editor = new EditorInternals();
|
||||
|
||||
class EditorInternals implements Editor {
|
||||
|
||||
Map<String, Object> innerMap = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Editor putString(String k, @Nullable String v) {
|
||||
innerMap.put(k, v);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putStringSet(String k, @Nullable Set<String> set) {
|
||||
innerMap.put(k, set);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putInt(String k, int i) {
|
||||
innerMap.put(k, i);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putLong(String k, long l) {
|
||||
innerMap.put(k, l);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putFloat(String k, float v) {
|
||||
innerMap.put(k, v);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putBoolean(String k, boolean b) {
|
||||
innerMap.put(k, b);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor remove(String k) {
|
||||
innerMap.remove(k);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor clear() {
|
||||
innerMap.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean commit() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ?> getAll() {
|
||||
return editor.innerMap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getString(String k, @Nullable String s) {
|
||||
if (editor.innerMap.containsKey(k)) {
|
||||
return (String) editor.innerMap.get(k);
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Set<String> getStringSet(String k, @Nullable Set<String> set) {
|
||||
if (editor.innerMap.containsKey(k)) {
|
||||
return (Set<String>) editor.innerMap.get(k);
|
||||
} else {
|
||||
return set;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String k, int i) {
|
||||
if (editor.innerMap.containsKey(k)) {
|
||||
return (Integer) editor.innerMap.get(k);
|
||||
} else {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(String k, long l) {
|
||||
if (editor.innerMap.containsKey(k)) {
|
||||
return (Long) editor.innerMap.get(k);
|
||||
} else {
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(String k, float v) {
|
||||
if (editor.innerMap.containsKey(k)) {
|
||||
return (Float) editor.innerMap.get(k);
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String k, boolean b) {
|
||||
if (editor.innerMap.containsKey(k)) {
|
||||
return (Boolean) editor.innerMap.get(k);
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String k) {
|
||||
return editor.innerMap.containsKey(k);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor edit() {
|
||||
return editor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package info.nightscout.androidaps.testing.utils
|
||||
|
||||
import info.nightscout.androidaps.utils.storage.Storage
|
||||
import java.io.File
|
||||
|
||||
class SingleStringStorage : Storage {
|
||||
|
||||
var contents: String = ""
|
||||
|
||||
constructor(contents: String) {
|
||||
this.contents = contents
|
||||
}
|
||||
|
||||
override fun getFileContents(file: File): String {
|
||||
return contents
|
||||
}
|
||||
|
||||
override fun putFileContents(file: File, putContents: String) {
|
||||
contents = putContents
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return contents
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue