food -> Room

This commit is contained in:
Milos Kozak 2021-03-19 18:49:34 +01:00
parent 3d4d27439a
commit a2fbab02f9
34 changed files with 3437 additions and 643 deletions

View file

@ -109,6 +109,7 @@ class MainApp : DaggerApplication() {
filter.addAction(Intents.ACTION_NEW_PROFILE)
filter.addAction(Intents.ACTION_NEW_MBG)
filter.addAction(Intents.ACTION_NEW_CAL)
filter.addAction(Intents.ACTION_FOOD)
LocalBroadcastManager.getInstance(this).registerReceiver(DataReceiver(), filter)
filter = IntentFilter()
filter.addAction(Intent.ACTION_TIME_CHANGED)

View file

@ -1,9 +1,11 @@
package info.nightscout.androidaps.db
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.Food
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.database.entities.TherapyEvent
import info.nightscout.androidaps.events.EventFoodDatabaseChanged
import info.nightscout.androidaps.events.EventNewBG
import info.nightscout.androidaps.events.EventTempTargetChange
import info.nightscout.androidaps.events.EventTherapyEventChange
@ -44,5 +46,9 @@ class CompatDBHelper @Inject constructor(
aapsLogger.debug(LTag.DATABASE, "Firing EventTherapyEventChange")
rxBus.send(EventTherapyEventChange())
}
it.filterIsInstance<Food>().firstOrNull()?.let {
aapsLogger.debug(LTag.DATABASE, "Firing EventFoodDatabaseChanged")
rxBus.send(EventFoodDatabaseChanged())
}
}
}

View file

@ -3,7 +3,6 @@ package info.nightscout.androidaps.dependencyInjection
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.db.DatabaseHelper
import info.nightscout.androidaps.plugins.general.food.FoodService
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
import info.nightscout.androidaps.plugins.treatments.TreatmentService
import info.nightscout.androidaps.utils.wizard.BolusWizard
@ -17,7 +16,6 @@ abstract class DataClassesModule {
@ContributesAndroidInjector abstract fun DatabaseHelperInjector(): DatabaseHelper
@ContributesAndroidInjector abstract fun treatmentServiceInjector(): TreatmentService
@ContributesAndroidInjector abstract fun foodServiceInjector(): FoodService
@ContributesAndroidInjector abstract fun bolusWizardInjector(): BolusWizard
@ContributesAndroidInjector abstract fun quickWizardEntryInjector(): QuickWizardEntry

View file

@ -2,6 +2,7 @@ package info.nightscout.androidaps.dependencyInjection
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.plugins.general.food.FoodPlugin
import info.nightscout.androidaps.plugins.general.nsclient.NSClientWorker
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin
@ -22,4 +23,5 @@ abstract class WorkersModule {
@ContributesAndroidInjector abstract fun contributesNSProfileWorker(): NSProfilePlugin.NSProfileWorker
@ContributesAndroidInjector abstract fun contributesSmsCommunicatorWorker(): SmsCommunicatorPlugin.SmsCommunicatorWorker
@ContributesAndroidInjector abstract fun contributesNSClientWorker(): NSClientWorker
@ContributesAndroidInjector abstract fun contributesFoodWorker(): FoodPlugin.FoodWorker
}

View file

@ -1,20 +0,0 @@
package info.nightscout.androidaps.events
import org.json.JSONArray
/**
* Event which is published with data fetched from NightScout specific for the
* Food-class.
*
* Payload is the from NS retrieved JSON-String which should be handled by all
* subscriber.
*/
class EventNsFood(val mode: Int, val foods: JSONArray) : Event() {
companion object {
val ADD = 0
val UPDATE = 1
val REMOVE = 2
}
}

View file

@ -1,145 +0,0 @@
package info.nightscout.androidaps.plugins.general.food;
import androidx.annotation.NonNull;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Objects;
import info.nightscout.androidaps.utils.JsonHelper;
/**
* Created by mike on 20.09.2017.
*/
@DatabaseTable(tableName = Food.TABLE_FOODS)
public class Food {
static final String TABLE_FOODS = "Foods";
@DatabaseField(id = true)
public long key;
@DatabaseField
public boolean isValid = true;
@DatabaseField
public String _id; // NS _id
@DatabaseField
public String name;
@DatabaseField
public String category;
@DatabaseField
public String subcategory;
// Example:
// name="juice" portion=250 units="ml" carbs=12
// means 250ml of juice has 12g of carbs
@DatabaseField
public double portion; // common portion in "units"
@DatabaseField
public int carbs; // in grams
@DatabaseField
public int fat = 0; // in grams
@DatabaseField
public int protein = 0; // in grams
@DatabaseField
public int energy = 0; // in kJ
@DatabaseField
public String units = "g";
@DatabaseField
public int gi; // not used yet
private Food() {
key = System.currentTimeMillis();
}
public static Food createFromJson(JSONObject json) throws JSONException {
Food food = new Food();
if ("food".equals(JsonHelper.safeGetString(json, "type"))) {
food._id = JsonHelper.safeGetString(json, "_id");
food.category = JsonHelper.safeGetString(json, "category");
food.subcategory = JsonHelper.safeGetString(json, "subcategory");
food.name = JsonHelper.safeGetString(json, "name");
food.units = JsonHelper.safeGetString(json, "unit");
food.portion = JsonHelper.safeGetDouble(json, "portion");
food.carbs = JsonHelper.safeGetInt(json, "carbs");
food.gi = JsonHelper.safeGetInt(json, "gi");
food.energy = JsonHelper.safeGetInt(json, "energy");
food.protein = JsonHelper.safeGetInt(json, "protein");
food.fat = JsonHelper.safeGetInt(json, "fat");
}
return food;
}
public boolean isEqual(Food other) {
if (portion != other.portion)
return false;
if (carbs != other.carbs)
return false;
if (fat != other.fat)
return false;
if (protein != other.protein)
return false;
if (energy != other.energy)
return false;
if (gi != other.gi)
return false;
if (!Objects.equals(_id, other._id))
return false;
if (!Objects.equals(name, other.name))
return false;
if (!Objects.equals(category, other.category))
return false;
if (!Objects.equals(subcategory, other.subcategory))
return false;
return Objects.equals(units, other.units);
}
public void copyFrom(Food other) {
isValid = other.isValid;
_id = other._id;
name = other.name;
category = other.category;
subcategory = other.subcategory;
portion = other.portion;
carbs = other.carbs;
fat = other.fat;
protein = other.protein;
energy = other.energy;
units = other.units;
gi = other.gi;
}
@Override @NonNull
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("_id=" + _id + ";");
sb.append("isValid=" + isValid + ";");
sb.append("name=" + name + ";");
sb.append("category=" + category + ";");
sb.append("subcategory=" + subcategory + ";");
sb.append("portion=" + portion + ";");
sb.append("carbs=" + carbs + ";");
sb.append("protein=" + protein + ";");
sb.append("energy=" + energy + ";");
sb.append("units=" + units + ";");
sb.append("gi=" + gi + ";");
return sb.toString();
}
}

View file

@ -1,7 +1,6 @@
package info.nightscout.androidaps.plugins.general.food
import android.annotation.SuppressLint
import android.content.DialogInterface
import android.graphics.Paint
import android.os.Bundle
import android.text.Editable
@ -15,20 +14,30 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.entities.UserEntry.*
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.Food
import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.transactions.InvalidateFoodTransaction
import info.nightscout.androidaps.databinding.FoodFragmentBinding
import info.nightscout.androidaps.databinding.FoodItemBinding
import info.nightscout.androidaps.events.EventFoodDatabaseChanged
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.food.FoodFragment.RecyclerViewAdapter.FoodsViewHolder
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.extensions.toVisibility
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.Completable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxkotlin.subscribeBy
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.collections.ArrayList
@ -36,10 +45,11 @@ class FoodFragment : DaggerFragment() {
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var foodPlugin: FoodPlugin
@Inject lateinit var nsUpload: NSUpload
@Inject lateinit var repository: AppRepository
@Inject lateinit var uel: UserEntryLogger
private val disposable = CompositeDisposable()
@ -62,8 +72,23 @@ class FoodFragment : DaggerFragment() {
binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
binding.recyclerview.adapter = RecyclerViewAdapter(foodPlugin.service?.foodData
?: ArrayList())
binding.refreshFromNightscout.setOnClickListener {
context?.let { context ->
OKDialog.showConfirmation(context, resourceHelper.gs(R.string.refresheventsfromnightscout) + " ?", {
uel.log(Action.FOOD_FROM_NS)
disposable += Completable.fromAction { repository.deleteAllFoods() }
.subscribeOn(aapsSchedulers.io)
.observeOn(aapsSchedulers.main)
.subscribeBy(
onError = { aapsLogger.error("Error removing foods", it) },
onComplete = { rxBus.send(EventFoodDatabaseChanged()) }
)
rxBus.send(EventNSClientRestart())
})
}
}
binding.clearfilter.setOnClickListener {
binding.filter.setText("")
@ -99,10 +124,6 @@ class FoodFragment : DaggerFragment() {
override fun afterTextChanged(s: Editable) {}
})
loadData()
fillCategories()
fillSubcategories()
filterData()
}
@Synchronized
@ -111,9 +132,23 @@ class FoodFragment : DaggerFragment() {
disposable.add(rxBus
.toObservable(EventFoodDatabaseChanged::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ updateGui() }, fabricPrivacy::logException)
.debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
)
updateGui()
swapAdapter()
}
private fun swapAdapter() {
disposable += repository
.getFoodData()
.observeOn(aapsSchedulers.main)
.subscribe { list ->
unfiltered = list
fillCategories()
fillSubcategories()
filterData()
binding.recyclerview.swapAdapter(RecyclerViewAdapter(filtered), true)
}
}
@Synchronized
@ -128,14 +163,11 @@ class FoodFragment : DaggerFragment() {
_binding = null
}
private fun loadData() {
unfiltered = foodPlugin.service?.foodData ?: ArrayList()
}
private fun fillCategories() {
val catSet: MutableSet<CharSequence> = HashSet()
for (f in unfiltered) {
if (f.category != null && f.category != "") catSet.add(f.category)
val category = f.category
if (!category.isNullOrBlank()) catSet.add(category)
}
// make it unique
val categories = ArrayList(catSet)
@ -151,7 +183,10 @@ class FoodFragment : DaggerFragment() {
val subCatSet: MutableSet<CharSequence> = HashSet()
if (categoryFilter != resourceHelper.gs(R.string.none)) {
for (f in unfiltered) {
if (f.category != null && f.category == categoryFilter) if (f.subcategory != null && f.subcategory != "") subCatSet.add(f.subcategory)
if (f.category != null && f.category == categoryFilter) {
val subCategory = f.subCategory
if (!subCategory.isNullOrEmpty()) subCatSet.add(subCategory)
}
}
}
// make it unique
@ -169,21 +204,19 @@ class FoodFragment : DaggerFragment() {
val subcategoryFilter = binding.subcategory.selectedItem.toString()
val newFiltered = ArrayList<Food>()
for (f in unfiltered) {
if (f.name == null || f.category == null || f.subcategory == null) continue
if (subcategoryFilter != resourceHelper.gs(R.string.none) && f.subcategory != subcategoryFilter) continue
if (f.category == null || f.subCategory == null) continue
if (subcategoryFilter != resourceHelper.gs(R.string.none) && f.subCategory != subcategoryFilter) continue
if (categoryFilter != resourceHelper.gs(R.string.none) && f.category != categoryFilter) continue
if (textFilter != "" && !f.name.toLowerCase(Locale.getDefault()).contains(textFilter.toLowerCase(Locale.getDefault()))) continue
newFiltered.add(f)
}
filtered = newFiltered
updateGui()
}
private fun updateGui() {
binding.recyclerview.swapAdapter(RecyclerViewAdapter(filtered), true)
}
inner class RecyclerViewAdapter internal constructor(var foodList: List<Food>) : RecyclerView.Adapter<FoodsViewHolder>() {
fun Int?.isNotZero(): Boolean = this != null && this != 0
inner class RecyclerViewAdapter internal constructor(private var foodList: List<Food>) : RecyclerView.Adapter<RecyclerViewAdapter.FoodsViewHolder>() {
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): FoodsViewHolder {
val v = LayoutInflater.from(viewGroup.context).inflate(R.layout.food_item, viewGroup, false)
@ -193,16 +226,16 @@ class FoodFragment : DaggerFragment() {
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: FoodsViewHolder, position: Int) {
val food = foodList[position]
holder.binding.nsSign.visibility = if (food._id != null) View.VISIBLE else View.GONE
holder.binding.nsSign.visibility = (food.interfaceIDs.nightscoutId != null).toVisibility()
holder.binding.name.text = food.name
holder.binding.portion.text = food.portion.toString() + food.units
holder.binding.portion.text = food.portion.toString() + food.unit
holder.binding.carbs.text = food.carbs.toString() + resourceHelper.gs(R.string.shortgramm)
holder.binding.fat.text = resourceHelper.gs(R.string.shortfat) + ": " + food.fat + resourceHelper.gs(R.string.shortgramm)
if (food.fat == 0) holder.binding.fat.visibility = View.INVISIBLE
holder.binding.fat.visibility = food.fat.isNotZero().toVisibility()
holder.binding.protein.text = resourceHelper.gs(R.string.shortprotein) + ": " + food.protein + resourceHelper.gs(R.string.shortgramm)
if (food.protein == 0) holder.binding.protein.visibility = View.INVISIBLE
holder.binding.protein.visibility = food.protein.isNotZero().toVisibility()
holder.binding.energy.text = resourceHelper.gs(R.string.shortenergy) + ": " + food.energy + resourceHelper.gs(R.string.shortkilojoul)
if (food.energy == 0) holder.binding.energy.visibility = View.INVISIBLE
holder.binding.energy.visibility = food.energy.isNotZero().toVisibility()
holder.binding.remove.tag = food
}
@ -216,12 +249,17 @@ class FoodFragment : DaggerFragment() {
binding.remove.setOnClickListener { v: View ->
val food = v.tag as Food
activity?.let { activity ->
OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.confirmation), resourceHelper.gs(R.string.removerecord) + "\n" + food.name, DialogInterface.OnClickListener { _: DialogInterface?, _: Int ->
OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.removerecord) + "\n" + food.name, {
uel.log(Action.FOOD_REMOVED, food.name)
if (food._id != null && food._id != "") {
nsUpload.removeFoodFromNS(food._id)
}
foodPlugin.service?.delete(food)
disposable += repository.runTransactionForResult(InvalidateFoodTransaction(food.id))
.subscribe({
val id = food.interfaceIDs.nightscoutId
if (NSUpload.isIdValid(id)) nsUpload.removeFoodFromNS(id)
// no create at the moment
// else uploadQueue.removeID("dbAdd", food.timestamp.toString())
}, {
aapsLogger.error(LTag.BGSOURCE, "Error while invalidating food", it)
})
}, null)
}
}

View file

@ -1,12 +1,28 @@
package info.nightscout.androidaps.plugins.general.food
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.Food
import info.nightscout.androidaps.database.transactions.SyncFoodTransaction
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.receivers.BundleStore
import info.nightscout.androidaps.receivers.DataReceiver
import info.nightscout.androidaps.utils.JsonHelper
import info.nightscout.androidaps.utils.extensions.foodFromJson
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import org.json.JSONArray
import org.json.JSONObject
import javax.inject.Inject
import javax.inject.Singleton
@ -25,10 +41,75 @@ class FoodPlugin @Inject constructor(
aapsLogger, resourceHelper, injector
) {
var service: FoodService? = null
private val disposable = CompositeDisposable()
override fun onStart() {
super.onStart()
service = FoodService(injector)
// cannot be inner class because of needed injection
class FoodWorker(
context: Context,
params: WorkerParameters
) : Worker(context, params) {
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var repository: AppRepository
@Inject lateinit var sp: SP
@Inject lateinit var bundleStore: BundleStore
@Inject lateinit var foodPlugin: FoodPlugin
init {
(context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
}
override fun doWork(): Result {
aapsLogger.debug(LTag.DATAFOOD, "Received Food Data: $inputData}")
val bundle = bundleStore.pickup(inputData.getLong(DataReceiver.STORE_KEY, -1))
?: return Result.failure()
var ret = Result.success()
val foodsString = bundle.getString("foods") ?: return Result.failure()
val foods = JSONArray(foodsString)
for (index in 0 until foods.length()) {
val jsonFood: JSONObject = foods.getJSONObject(index)
if (JsonHelper.safeGetString(jsonFood, "type") != "food") continue
when (JsonHelper.safeGetString(jsonFood, "action")) {
"remove" -> {
val delFood = Food(
name = "",
portion = 0.0,
carbs = 0,
isValid = false
).also { it.interfaceIDs.nightscoutId = JsonHelper.safeGetString(jsonFood, "_id") }
foodPlugin.disposable += repository.runTransactionForResult(SyncFoodTransaction(delFood)).subscribe({ result ->
result.invalidated.forEach { aapsLogger.debug(LTag.DATAFOOD, "Invalidated food ${it.interfaceIDs.nightscoutId}") }
}, {
aapsLogger.error(LTag.DATAFOOD, "Error while removing food", it)
ret = Result.failure()
})
}
else -> {
val food = foodFromJson(jsonFood)
if (food != null) {
foodPlugin.disposable += repository.runTransactionForResult(SyncFoodTransaction(food)).subscribe({ result ->
result.inserted.forEach { aapsLogger.debug(LTag.DATAFOOD, "Inserted food $it") }
result.updated.forEach { aapsLogger.debug(LTag.DATAFOOD, "Updated food $it") }
result.invalidated.forEach { aapsLogger.debug(LTag.DATAFOOD, "Invalidated food $it") }
}, {
aapsLogger.error(LTag.DATAFOOD, "Error while adding/updating food", it)
ret = Result.failure()
})
} else {
aapsLogger.error(LTag.DATAFOOD, "Error parsing food", jsonFood.toString())
ret = Result.failure()
}
}
}
}
return ret
}
}
}

View file

@ -1,359 +0,0 @@
package info.nightscout.androidaps.plugins.general.food;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.Nullable;
import com.j256.ormlite.android.apptools.OpenHelperManager;
import com.j256.ormlite.android.apptools.OrmLiteBaseService;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import dagger.android.HasAndroidInjector;
import info.nightscout.androidaps.db.DatabaseHelper;
import info.nightscout.androidaps.db.ICallback;
import info.nightscout.androidaps.events.Event;
import info.nightscout.androidaps.events.EventFoodDatabaseChanged;
import info.nightscout.androidaps.events.EventNsFood;
import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
import info.nightscout.androidaps.utils.FabricPrivacy;
import info.nightscout.androidaps.utils.rx.AapsSchedulers;
import io.reactivex.disposables.CompositeDisposable;
/**
* Created by mike on 24.09.2017.
*/
public class FoodService extends OrmLiteBaseService<DatabaseHelper> {
@Inject AAPSLogger aapsLogger;
@Inject RxBusWrapper rxBus;
@Inject FabricPrivacy fabricPrivacy;
@Inject AapsSchedulers aapsSchedulers;
private final CompositeDisposable disposable = new CompositeDisposable();
private static final ScheduledExecutorService foodEventWorker = Executors.newSingleThreadScheduledExecutor();
private static ScheduledFuture<?> scheduledFoodEventPost = null;
public FoodService(HasAndroidInjector injector) {
injector.androidInjector().inject(this);
onCreate();
dbInitialize();
disposable.add(rxBus
.toObservable(EventNsFood.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(event -> {
int mode = event.getMode();
JSONArray array = event.getFoods();
if (mode == EventNsFood.Companion.getADD() || mode == EventNsFood.Companion.getUPDATE())
this.createFoodFromJsonIfNotExists(array);
else
this.deleteNS(array);
}, fabricPrivacy::logException)
);
}
/**
* This method is a simple re-implementation of the database create and up/downgrade functionality
* in SQLiteOpenHelper#getDatabaseLocked method.
* <p>
* It is implemented to be able to late initialize separate plugins of the application.
*/
protected void dbInitialize() {
DatabaseHelper helper = OpenHelperManager.getHelper(this, DatabaseHelper.class);
int newVersion = helper.getNewVersion();
int oldVersion = helper.getOldVersion();
if (oldVersion > newVersion) {
onDowngrade(this.getConnectionSource(), oldVersion, newVersion);
} else {
onUpgrade(this.getConnectionSource(), oldVersion, newVersion);
}
}
public Dao<Food, Long> getDao() {
try {
return DaoManager.createDao(this.getConnectionSource(), Food.class);
} catch (SQLException e) {
aapsLogger.error("Cannot create Dao for Food.class");
}
return null;
}
@Override
public void onCreate() {
super.onCreate();
try {
aapsLogger.info(LTag.DATAFOOD, "onCreate");
TableUtils.createTableIfNotExists(this.getConnectionSource(), Food.class);
} catch (SQLException e) {
aapsLogger.error("Can't create database", e);
throw new RuntimeException(e);
}
}
public void onUpgrade(ConnectionSource connectionSource, int oldVersion, int newVersion) {
aapsLogger.info(LTag.DATAFOOD, "onUpgrade");
// this.resetFood();
}
public void onDowngrade(ConnectionSource connectionSource, int oldVersion, int newVersion) {
// this method is not supported right now
}
public void resetFood() {
try {
TableUtils.dropTable(this.getConnectionSource(), Food.class, true);
TableUtils.createTableIfNotExists(this.getConnectionSource(), Food.class);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
scheduleFoodChange();
}
/**
* A place to centrally register events to be posted, if any data changed.
* This should be implemented in an abstract service-class.
* <p>
* We do need to make sure, that ICallback is extended to be able to handle multiple
* events, or handle a list of events.
* <p>
* on some methods the earliestDataChange event is handled separatly, in that it is checked if it is
* set to null by another event already (eg. scheduleExtendedBolusChange).
*
* @param event
* @param eventWorker
* @param callback
*/
private void scheduleEvent(final Event event, ScheduledExecutorService eventWorker,
final ICallback callback) {
class PostRunnable implements Runnable {
public void run() {
aapsLogger.debug(LTag.DATAFOOD, "Firing EventFoodChange");
rxBus.send(event);
callback.setPost(null);
}
}
// prepare task for execution in 1 sec
// cancel waiting task to prevent sending multiple posts
if (callback.getPost() != null)
callback.getPost().cancel(false);
Runnable task = new PostRunnable();
final int sec = 1;
callback.setPost(eventWorker.schedule(task, sec, TimeUnit.SECONDS));
}
/**
* Schedule a foodChange Event.
*/
public void scheduleFoodChange() {
this.scheduleEvent(new EventFoodDatabaseChanged(), foodEventWorker, new ICallback() {
@Override
public void setPost(ScheduledFuture<?> post) {
scheduledFoodEventPost = post;
}
@Override
public ScheduledFuture<?> getPost() {
return scheduledFoodEventPost;
}
});
}
public List<Food> getFoodData() {
try {
return this.getDao().queryForAll();
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return new ArrayList<>();
}
/*
{
"_id": "551ee3ad368e06e80856e6a9",
"type": "food",
"category": "Zakladni",
"subcategory": "Napoje",
"name": "Mleko",
"portion": 250,
"carbs": 12,
"gi": 1,
"created_at": "2015-04-14T06:59:16.500Z",
"unit": "ml"
}
*/
public void createFoodFromJsonIfNotExists(JSONObject json) {
try {
Food food = Food.createFromJson(json);
this.createFoodFromJsonIfNotExists(food);
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public void createFoodFromJsonIfNotExists(JSONArray array) {
try {
for (int n = 0; n < array.length(); n++) {
JSONObject json = array.getJSONObject(n);
Food food = Food.createFromJson(json);
this.createFoodFromJsonIfNotExists(food);
}
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public void createFoodFromJsonIfNotExists(Food food) {
this.createOrUpdateByNS(food);
}
public void deleteNS(JSONObject json) {
try {
String _id = json.getString("_id");
this.deleteByNSId(_id);
} catch (JSONException | SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public void deleteNS(JSONArray array) {
try {
for (int n = 0; n < array.length(); n++) {
JSONObject json = array.getJSONObject(n);
this.deleteNS(json);
}
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
}
/**
* deletes an entry by its NS Id.
* <p>
* Basically a convenience method for findByNSId and delete.
*
* @param _id
*/
public void deleteByNSId(String _id) throws SQLException {
Food stored = this.findByNSId(_id);
if (stored != null) {
aapsLogger.debug(LTag.DATAFOOD, "Removing Food record from database: " + stored.toString());
this.delete(stored);
}
}
/**
* deletes the food and sends the foodChange Event
* <p>
* should be moved ot a Service
*
* @param food
*/
public void delete(Food food) {
try {
this.getDao().delete(food);
this.scheduleFoodChange();
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
}
/**
* Create of update a food record by the NS (Nightscout) Id.
*
* @param food
* @return
*/
public boolean createOrUpdateByNS(Food food) {
// find by NS _id
if (food._id != null && !food._id.equals("")) {
Food old = this.findByNSId(food._id);
if (old != null) {
if (!old.isEqual(food)) {
this.delete(old); // need to delete/create because date may change too
old.copyFrom(food);
this.create(old);
return true;
} else {
return false;
}
} else {
this.createOrUpdate(food);
return true;
}
}
return false;
}
public void createOrUpdate(Food food) {
try {
this.getDao().createOrUpdate(food);
aapsLogger.debug(LTag.DATAFOOD, "Created or Updated: " + food.toString());
} catch (SQLException e) {
aapsLogger.error("Unable to createOrUpdate Food", e);
}
this.scheduleFoodChange();
}
public void create(Food food) {
try {
this.getDao().create(food);
aapsLogger.debug(LTag.DATAFOOD, "New record: " + food.toString());
} catch (SQLException e) {
aapsLogger.error("Unable to create Food", e);
}
this.scheduleFoodChange();
}
/**
* finds food by its NS Id.
*
* @param _id
* @return
*/
@Nullable
public Food findByNSId(String _id) {
try {
List<Food> list = this.getDao().queryForEq("_id", _id);
if (list.size() == 1) { // really? if there are more then one result, then we do not return anything...
return list.get(0);
}
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return null;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

View file

@ -108,6 +108,7 @@ class ImportExportPrefs @Inject constructor(
return metadata
}
@Suppress("SpellCheckingInspection")
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")
@ -346,7 +347,7 @@ class ImportExportPrefs @Inject constructor(
private fun restartAppAfterImport(context: Context) {
sp.putBoolean(R.string.key_setupwizard_processed, true)
OKDialog.show(context, resourceHelper.gs(R.string.setting_imported), resourceHelper.gs(R.string.restartingapp), Runnable {
OKDialog.show(context, resourceHelper.gs(R.string.setting_imported), resourceHelper.gs(R.string.restartingapp)) {
uel.log(Action.IMPORT_SETTINGS)
log.debug(LTag.CORE, "Exiting")
rxBus.send(EventAppExit())
@ -355,14 +356,13 @@ class ImportExportPrefs @Inject constructor(
}
System.runFinalization()
exitProcess(0)
})
}
}
override fun exportUserEntriesCsv(activity: FragmentActivity, listEntries: Single<List<UserEntry>>) {
val entries = listEntries.blockingGet()
override fun exportUserEntriesCsv(activity: FragmentActivity, singleEntries: Single<List<UserEntry>>) {
val entries = singleEntries.blockingGet()
prefFileList.ensureExportDirExists()
val newFile = prefFileList.newExportXmlFile()
//log.debug("XXXXX " + classicPrefsFormat.UserEntriesToCsv(entries))
try {
classicPrefsFormat.saveCsv(newFile, entries)

View file

@ -16,7 +16,6 @@ import info.nightscout.androidaps.interfaces.ImportExportPrefsInterface
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.food.FoodPlugin
import info.nightscout.androidaps.plugins.general.maintenance.activities.LogSettingActivity
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
@ -34,7 +33,6 @@ class MaintenanceFragment : DaggerFragment() {
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var treatmentsPlugin: TreatmentsPlugin
@Inject lateinit var foodPlugin: FoodPlugin
@Inject lateinit var importExportPrefs: ImportExportPrefsInterface
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var repository: AppRepository
@ -70,7 +68,6 @@ class MaintenanceFragment : DaggerFragment() {
databaseHelper.resetDatabases()
// should be handled by Plugin-Interface and
// additional service interface and plugin registry
foodPlugin.service?.resetFood()
treatmentsPlugin.service.resetTreatments()
repository.clearDatabases()
}

View file

@ -34,7 +34,6 @@ import info.nightscout.androidaps.R;
import info.nightscout.androidaps.db.DbRequest;
import info.nightscout.androidaps.events.EventAppExit;
import info.nightscout.androidaps.events.EventConfigBuilderChange;
import info.nightscout.androidaps.events.EventNsFood;
import info.nightscout.androidaps.events.EventPreferenceChange;
import info.nightscout.androidaps.interfaces.DatabaseHelperInterface;
import info.nightscout.androidaps.interfaces.PluginType;
@ -636,39 +635,8 @@ public class NSClientService extends DaggerService {
}
if (data.has("food")) {
JSONArray foods = data.getJSONArray("food");
JSONArray removedFoods = new JSONArray();
JSONArray updatedFoods = new JSONArray();
JSONArray addedFoods = new JSONArray();
if (foods.length() > 0)
rxBus.send(new EventNSClientNewLog("DATA", "received " + foods.length() + " foods"));
for (Integer index = 0; index < foods.length(); index++) {
JSONObject jsonFood = foods.getJSONObject(index);
// remove from upload queue if Ack is failing
uploadQueue.removeID(jsonFood);
String action = JsonHelper.safeGetString(jsonFood, "action");
if (action == null) {
addedFoods.put(jsonFood);
} else if (action.equals("update")) {
updatedFoods.put(jsonFood);
} else if (action.equals("remove")) {
removedFoods.put(jsonFood);
}
}
if (removedFoods.length() > 0) {
EventNsFood evt = new EventNsFood(EventNsFood.Companion.getREMOVE(), removedFoods);
rxBus.send(evt);
}
if (updatedFoods.length() > 0) {
EventNsFood evt = new EventNsFood(EventNsFood.Companion.getUPDATE(), updatedFoods);
rxBus.send(evt);
}
if (addedFoods.length() > 0) {
EventNsFood evt = new EventNsFood(EventNsFood.Companion.getADD(), addedFoods);
rxBus.send(evt);
}
if (foods.length() > 0) rxBus.send(new EventNSClientNewLog("DATA", "received " + foods.length() + " foods"));
handleFood(foods, isDelta);
}
if (data.has("mbgs")) {
JSONArray mbgs = data.getJSONArray("mbgs");
@ -881,6 +849,16 @@ public class NSClientService extends DaggerService {
}
}
public void handleFood(JSONArray foods, boolean isDelta) {
Bundle bundle = new Bundle();
bundle.putString("foods", foods.toString());
bundle.putBoolean("delta", isDelta);
Intent intent = new Intent(Intents.ACTION_FOOD);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
public void handleNewCal(JSONArray cals, boolean isDelta) {
Bundle bundle = new Bundle();
bundle.putString("cals", cals.toString());

View file

@ -12,6 +12,7 @@ import dagger.android.DaggerBroadcastReceiver
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.BundleLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.general.food.FoodPlugin
import info.nightscout.androidaps.plugins.general.nsclient.NSClientWorker
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin
@ -21,7 +22,6 @@ import info.nightscout.androidaps.utils.extensions.copyDouble
import info.nightscout.androidaps.utils.extensions.copyInt
import info.nightscout.androidaps.utils.extensions.copyLong
import info.nightscout.androidaps.utils.extensions.copyString
import org.json.JSONObject
import javax.inject.Inject
open class DataReceiver : DaggerBroadcastReceiver() {
@ -87,6 +87,9 @@ open class DataReceiver : DaggerBroadcastReceiver() {
Intents.DEXCOM_BG ->
OneTimeWorkRequest.Builder(DexcomPlugin.DexcomWorker::class.java)
.setInputData(bundleInputData(bundle, intent)).build()
Intents.ACTION_FOOD ->
OneTimeWorkRequest.Builder(FoodPlugin.FoodWorker::class.java)
.setInputData(bundleInputData(bundle, intent)).build()
Intents.ACTION_NEW_TREATMENT,
Intents.ACTION_CHANGED_TREATMENT,
Intents.ACTION_REMOVED_TREATMENT,
@ -105,6 +108,7 @@ open class DataReceiver : DaggerBroadcastReceiver() {
Data.Builder().putLong(STORE_KEY, bundleStore.store(bundle)).putString(ACTION_KEY, intent.action).build()
companion object {
const val STORE_KEY = "storeKey"
const val ACTION_KEY = "action"
}

View file

@ -13,6 +13,7 @@ interface Intents {
const val ACTION_NEW_SGV = "info.nightscout.client.NEW_SGV"
const val ACTION_NEW_MBG = "info.nightscout.client.NEW_MBG"
const val ACTION_NEW_CAL = "info.nightscout.client.NEW_CAL"
const val ACTION_FOOD = "info.nightscout.client.FOOD"
// xDrip -> App
const val ACTION_NEW_BG_ESTIMATE = "com.eveningoutpost.dexdrip.BgEstimate"

View file

@ -4,7 +4,17 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsExtendedBolusesFragment">
tools:context="info.nightscout.androidaps.plugins.general.food.TreatmentsFoodFragment">
<info.nightscout.androidaps.utils.ui.SingleClickButton
android:id="@+id/refresh_from_nightscout"
style="?android:attr/buttonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:drawableStart="@drawable/ic_refresh"
android:text="@string/refresheventsfromnightscout"
tools:visibility="visible" />
<LinearLayout
android:layout_width="match_parent"
@ -48,6 +58,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:text="@string/category" />
<Spinner
@ -65,6 +76,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:text="@string/subcategory" />
<Spinner

View file

@ -1,13 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
card_view:cardBackgroundColor="@color/cardColorBackground"
card_view:cardCornerRadius="6dp"
card_view:cardUseCompatPadding="true"
card_view:contentPadding="6dp">
card_view:cardBackgroundColor="?android:colorBackground">
<LinearLayout
android:layout_width="match_parent"
@ -26,21 +24,24 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Name"
android:textStyle="bold" />
android:textStyle="bold"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/portion"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:gravity="end"
android:text="Portion" />
android:text="Portion"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/carbs"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:gravity="end"
android:text="Carbs" />
android:text="Carbs"
tools:ignore="HardcodedText" />
</LinearLayout>
@ -56,21 +57,24 @@
android:layout_width="70dp"
android:layout_height="wrap_content"
android:gravity="end"
android:text="Fat" />
android:text="Fat"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/protein"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:gravity="end"
android:text="Protein" />
android:text="Protein"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/energy"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:gravity="end"
android:text="Energy" />
android:text="Energy"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/ns_sign"
@ -79,7 +83,8 @@
android:width="30dp"
android:text="NS"
android:textAlignment="viewEnd"
android:textColor="@color/colorSetTempButton" />
android:textColor="@color/colorSetTempButton"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/remove"
@ -93,6 +98,15 @@
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="5dp"
android:background="@color/list_delimiter" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View file

@ -317,7 +317,7 @@
<string name="openapsma_autosensdata_label">Autosens data</string>
<string name="openapsma_scriptdebugdata_label">Script debug</string>
<string name="openapsama_useautosens">Use Autosens feature</string>
<string name="refresheventsfromnightscout">Refresh events from NS</string>
<string name="refresheventsfromnightscout">Refresh from NS</string>
<string name="deletefuturetreatments">Delete treatments in the future</string>
<string name="actions_shortname">ACT</string>
<string name="configbuilder_shortname">CONF</string>

View file

@ -14,5 +14,5 @@ interface ImportExportPrefsInterface {
fun prefsFileExists(): Boolean
fun verifyStoragePermissions(fragment: Fragment, onGranted: Runnable)
fun exportSharedPreferences(f: Fragment)
fun exportUserEntriesCsv(activity: FragmentActivity, entries: Single<List<UserEntry>>)
fun exportUserEntriesCsv(activity: FragmentActivity, singleEntries: Single<List<UserEntry>>)
}

View file

@ -5,7 +5,6 @@ import org.json.JSONObject
object JsonHelper {
@JvmStatic
fun safeGetObject(json: JSONObject?, fieldName: String, defaultValue: Any): Any {
var result = defaultValue
if (json != null && json.has(fieldName)) {
@ -17,7 +16,6 @@ object JsonHelper {
return result
}
@JvmStatic
fun safeGetJSONObject(json: JSONObject?, fieldName: String, defaultValue: JSONObject?): JSONObject? {
var result = defaultValue
if (json != null && json.has(fieldName)) {
@ -53,7 +51,6 @@ object JsonHelper {
return result
}
@JvmStatic
fun safeGetStringAllowNull(json: JSONObject?, fieldName: String, defaultValue: String?): String? {
var result = defaultValue
if (json != null && json.has(fieldName)) {
@ -77,7 +74,6 @@ object JsonHelper {
return result
}
@JvmStatic
fun safeGetDoubleAllowNull(json: JSONObject?, fieldName: String): Double? {
var result: Double? = null
if (json != null && json.has(fieldName)) {
@ -89,7 +85,6 @@ object JsonHelper {
return result
}
@JvmStatic
fun safeGetDouble(json: JSONObject?, fieldName: String, defaultValue: Double): Double {
var result = defaultValue
if (json != null && json.has(fieldName)) {
@ -105,7 +100,6 @@ object JsonHelper {
fun safeGetInt(json: JSONObject?, fieldName: String): Int =
safeGetInt(json, fieldName, 0)
@JvmStatic
fun safeGetInt(json: JSONObject?, fieldName: String, defaultValue: Int): Int {
var result = defaultValue
if (json != null && json.has(fieldName)) {
@ -117,6 +111,17 @@ object JsonHelper {
return result
}
fun safeGetIntAllowNull(json: JSONObject?, fieldName: String): Int? {
var result : Int? = null
if (json != null && json.has(fieldName)) {
try {
result = json.getInt(fieldName)
} catch (ignored: JSONException) {
}
}
return result
}
@JvmStatic
fun safeGetLong(json: JSONObject?, fieldName: String): Long {
var result: Long = 0

View file

@ -0,0 +1,40 @@
package info.nightscout.androidaps.utils.extensions
import info.nightscout.androidaps.database.entities.Food
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload
import info.nightscout.androidaps.utils.JsonHelper
import org.json.JSONObject
fun foodFromJson(jsonObject: JSONObject): Food? {
if ("food" == JsonHelper.safeGetString(jsonObject, "type")) {
val name = JsonHelper.safeGetStringAllowNull(jsonObject, "name", null) ?: return null
val category = JsonHelper.safeGetStringAllowNull(jsonObject, "category", null)
val subCategory = JsonHelper.safeGetStringAllowNull(jsonObject, "subcategory", null)
val unit = JsonHelper.safeGetString(jsonObject, "unit", "")
val portion = JsonHelper.safeGetDoubleAllowNull(jsonObject, "portion") ?: return null
val carbs = JsonHelper.safeGetIntAllowNull(jsonObject, "carbs") ?: return null
val gi = JsonHelper.safeGetIntAllowNull(jsonObject, "gi")
val energy = JsonHelper.safeGetIntAllowNull(jsonObject, "energy")
val protein = JsonHelper.safeGetIntAllowNull(jsonObject, "protein")
val fat = JsonHelper.safeGetIntAllowNull(jsonObject, "fat")
val id = JsonHelper.safeGetStringAllowNull(jsonObject, "_id", null) ?: return null
val isValid = JsonHelper.safeGetBoolean(jsonObject, NSUpload.ISVALID, true)
val food = Food(
name = name,
category = category,
subCategory = subCategory,
unit = unit,
portion = portion,
carbs = carbs,
gi = gi,
energy = energy,
protein = protein,
fat = fat,
isValid = isValid
)
food.interfaceIDs.nightscoutId = id
return food
}
return null
}

File diff suppressed because it is too large Load diff

View file

@ -6,13 +6,14 @@ import androidx.room.TypeConverters
import info.nightscout.androidaps.database.daos.*
import info.nightscout.androidaps.database.entities.*
const val DATABASE_VERSION = 6
const val DATABASE_VERSION = 7
@Database(version = DATABASE_VERSION,
entities = [APSResult::class, Bolus::class, BolusCalculatorResult::class, Carbs::class,
EffectiveProfileSwitch::class, ExtendedBolus::class, GlucoseValue::class, ProfileSwitch::class,
TemporaryBasal::class, TemporaryTarget::class, TherapyEvent::class, TotalDailyDose::class, APSResultLink::class,
MealLink::class, MultiwaveBolusLink::class, PreferenceChange::class, VersionChange::class, UserEntry::class],
MealLink::class, MultiwaveBolusLink::class, PreferenceChange::class, VersionChange::class, UserEntry::class,
Food::class],
exportSchema = true)
@TypeConverters(Converters::class)
internal abstract class AppDatabase : RoomDatabase() {
@ -53,4 +54,6 @@ internal abstract class AppDatabase : RoomDatabase() {
abstract val preferenceChangeDao: PreferenceChangeDao
abstract val foodDao: FoodDao
}

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.database
import info.nightscout.androidaps.database.entities.Food
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.database.entities.TherapyEvent
@ -170,6 +171,14 @@ class AppRepository @Inject internal constructor(
database.therapyEventDao.compatGetTherapyEventDataFromToTime(from, to)
.subscribeOn(Schedulers.io())
// FOOD
fun getFoodData(): Single<List<Food>> =
database.foodDao.getFoodData()
.subscribeOn(Schedulers.io())
fun deleteAllFoods() =
database.foodDao.deleteAllEntries()
}
@Suppress("USELESS_CAST")

View file

@ -34,4 +34,10 @@ open class DatabaseModule {
database.execSQL("CREATE TABLE userEntry (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `action` TEXT NOT NULL, `s` TEXT NOT NULL, `values` TEXT NOT NULL)")
}
}
private val migration6to7 = object : Migration(6, 7) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS foods (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `name` TEXT NOT NULL, `category` TEXT, `subCategory` TEXT, `portion` REAL NOT NULL, `carbs` INTEGER NOT NULL, `fat` INTEGER, `protein` INTEGER, `energy` INTEGER, `unit` TEXT NOT NULL, `gi` INTEGER, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `foods`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )")
}
}
}

View file

@ -24,5 +24,6 @@ internal class DelegatedAppDatabase(val changes: MutableList<DBEntry>, val datab
val versionChangeDao: VersionChangeDao = DelegatedVersionChangeDao(changes, database.versionChangeDao)
val userEntryDao: UserEntryDao = DelegatedUserEntryDao(changes, database.userEntryDao)
val preferenceChangeDao: PreferenceChangeDao = DelegatedPreferenceChangeDao(changes, database.preferenceChangeDao)
val foodDao: FoodDao = DelegatedFoodDao(changes, database.foodDao)
fun clearAllTables() = database.clearAllTables()
}

View file

@ -8,6 +8,7 @@ const val TABLE_CARBS = "carbs"
const val TABLE_EFFECTIVE_PROFILE_SWITCHES = "effectiveProfileSwitches"
const val TABLE_EXTENDED_BOLUSES = "extendedBoluses"
const val TABLE_GLUCOSE_VALUES = "glucoseValues"
const val TABLE_FOODS = "foods"
const val TABLE_MEAL_LINKS = "mealLinks"
const val TABLE_MULTIWAVE_BOLUS_LINKS = "multiwaveBolusLinks"
const val TABLE_PROFILE_SWITCHES = "profileSwitches"

View file

@ -0,0 +1,25 @@
package info.nightscout.androidaps.database.daos
import androidx.room.Dao
import androidx.room.Query
import info.nightscout.androidaps.database.TABLE_FOODS
import info.nightscout.androidaps.database.entities.Food
import io.reactivex.Single
@Suppress("FunctionName")
@Dao
internal interface FoodDao : TraceableDao<Food> {
@Query("SELECT * FROM $TABLE_FOODS WHERE id = :id")
override fun findById(id: Long): Food?
@Query("DELETE FROM $TABLE_FOODS")
override fun deleteAllEntries()
@Query("SELECT * FROM $TABLE_FOODS WHERE nightscoutId = :nsId AND referenceId IS NULL")
fun findByNSId(nsId: String): Food?
@Query("SELECT * FROM $TABLE_FOODS WHERE isValid = 1 AND referenceId IS NULL ORDER BY id DESC")
fun getFoodData(): Single<List<Food>>
}

View file

@ -4,7 +4,6 @@ import androidx.room.Insert
import androidx.room.Update
import info.nightscout.androidaps.database.daos.workaround.TraceableDaoWorkaround
import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
import io.reactivex.Single
internal interface TraceableDao<T : TraceableDBEntry> : TraceableDaoWorkaround<T> {

View file

@ -0,0 +1,18 @@
package info.nightscout.androidaps.database.daos.delegated
import info.nightscout.androidaps.database.daos.FoodDao
import info.nightscout.androidaps.database.entities.Food
import info.nightscout.androidaps.database.interfaces.DBEntry
internal class DelegatedFoodDao(changes: MutableList<DBEntry>, private val dao: FoodDao) : DelegatedDao(changes), FoodDao by dao {
override fun insertNewEntry(food: Food): Long {
changes.add(food)
return dao.insertNewEntry(food)
}
override fun updateExistingEntry(food: Food): Long {
changes.add(food)
return dao.updateExistingEntry(food)
}
}

View file

@ -0,0 +1,73 @@
package info.nightscout.androidaps.database.entities
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
import info.nightscout.androidaps.database.TABLE_FOODS
import info.nightscout.androidaps.database.embedments.InterfaceIDs
import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
@Entity(tableName = TABLE_FOODS,
foreignKeys = [ForeignKey(
entity = Food::class,
parentColumns = ["id"],
childColumns = ["referenceId"])],
indices = [Index("referenceId")])
data class Food(
@PrimaryKey(autoGenerate = true)
override var id: Long = 0,
override var version: Int = 0,
override var dateCreated: Long = -1,
override var isValid: Boolean = true,
override var referenceId: Long? = null,
@Embedded
override var interfaceIDs_backing: InterfaceIDs? = null,
var name: String,
var category: String? = null,
var subCategory: String? = null,
// Example:
// name="juice" portion=250 units="ml" carbs=12
// means 250ml of juice has 12g of carbs
var portion: Double, // common portion in "units"
var carbs: Int, // in grams
var fat: Int? = null, // in grams
var protein: Int? = null, // in grams
var energy: Int? = null, // in kJ
var unit: String = "g",
var gi: Int? = null // not used yet
) : TraceableDBEntry {
fun isEqual(other: Food): Boolean {
if (isValid != other.isValid) return false
if (portion != other.portion) return false
if (carbs != other.carbs) return false
if (fat != other.fat) return false
if (protein != other.protein) return false
if (energy != other.energy) return false
if (gi != other.gi) return false
if (name != other.name) return false
if (category != other.category) return false
if (subCategory != other.subCategory) return false
return unit == other.unit
}
fun copyFrom(other: Food) {
isValid = other.isValid
name = other.name
category = other.category
subCategory = other.subCategory
portion = other.portion
carbs = other.carbs
fat = other.fat
protein = other.protein
energy = other.energy
unit = other.unit
gi = other.gi
interfaceIDs.nightscoutId = other.interfaceIDs.nightscoutId
}
}

View file

@ -103,6 +103,7 @@ data class UserEntry(
@SerializedName("TT_DELETED_FROM_NS") TT_DELETED_FROM_NS (ColorGroup.TT),
@SerializedName("CAREPORTAL_DELETED_FROM_NS") CAREPORTAL_DELETED_FROM_NS (ColorGroup.Careportal),
@SerializedName("CAREPORTAL_FROM_NS") CAREPORTAL_FROM_NS (ColorGroup.Careportal),
@SerializedName("FOOD_FROM_NS") FOOD_FROM_NS (ColorGroup.Careportal),
@SerializedName("TT_FROM_NS") TT_FROM_NS (ColorGroup.TT),
@SerializedName("TT_CANCELED_FROM_NS") TT_CANCELED_FROM_NS (ColorGroup.TT),
@SerializedName("EXPORT_CSV") EXPORT_CSV (ColorGroup.Aaps),

View file

@ -0,0 +1,28 @@
package info.nightscout.androidaps.database.transactions
import info.nightscout.androidaps.database.entities.Food
/**
* Inserts or updates the Food
*/
class InsertOrUpdateFoodTransaction(private val food: Food) : Transaction<InsertOrUpdateFoodTransaction.TransactionResult>() {
override fun run(): TransactionResult {
val result = TransactionResult()
val current = database.foodDao.findById(food.id)
if (current == null) {
database.foodDao.insertNewEntry(food)
result.inserted.add(food)
} else {
database.foodDao.updateExistingEntry(food)
result.updated.add(food)
}
return result
}
class TransactionResult {
val inserted = mutableListOf<Food>()
val updated = mutableListOf<Food>()
}
}

View file

@ -0,0 +1,11 @@
package info.nightscout.androidaps.database.transactions
class InvalidateFoodTransaction(val id: Long) : Transaction<Unit>() {
override fun run() {
val food = database.foodDao.findById(id)
?: throw IllegalArgumentException("There is no such Food with the specified ID.")
food.isValid = false
database.foodDao.updateExistingEntry(food)
}
}

View file

@ -0,0 +1,42 @@
package info.nightscout.androidaps.database.transactions
import info.nightscout.androidaps.database.entities.Food
/**
* Sync the TherapyEvents from NS
*/
class SyncFoodTransaction(private val food: Food) : Transaction<SyncFoodTransaction.TransactionResult>() {
override fun run(): TransactionResult {
val result = TransactionResult()
val current: Food? =
food.interfaceIDs.nightscoutId?.let {
database.foodDao.findByNSId(it)
}
if (current != null) {
// nsId exists, update if different
if (!current.isEqual(food)) {
current.copyFrom(food)
database.foodDao.updateExistingEntry(current)
if (food.isValid && current.isValid) result.updated.add(current)
else if (!food.isValid && current.isValid) result.invalidated.add(current)
}
return result
}
// not known nsId, add
database.foodDao.insertNewEntry(food)
result.inserted.add(food)
return result
}
class TransactionResult {
val updated = mutableListOf<Food>()
val inserted = mutableListOf<Food>()
val invalidated = mutableListOf<Food>()
}
}