From 46295e50d37660c64c83be3838e9d90386daff66 Mon Sep 17 00:00:00 2001 From: Roman Tsukanov Date: Thu, 26 Dec 2024 22:03:44 -0800 Subject: [PATCH] Implement last update time storage --- .../me/tsukanov/counter/domain/Counter.java | 5 ++ .../counter/domain/IntegerCounter.java | 32 ++++++++ .../repository/SharedPrefsCounterStorage.java | 81 +++++++++++++------ app/src/main/res/values/strings.xml | 1 + 4 files changed, 94 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/me/tsukanov/counter/domain/Counter.java b/app/src/main/java/me/tsukanov/counter/domain/Counter.java index 3d2e4760..a2412f71 100644 --- a/app/src/main/java/me/tsukanov/counter/domain/Counter.java +++ b/app/src/main/java/me/tsukanov/counter/domain/Counter.java @@ -1,8 +1,10 @@ package me.tsukanov.counter.domain; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import me.tsukanov.counter.domain.exception.InvalidNameException; import me.tsukanov.counter.domain.exception.InvalidValueException; +import org.joda.time.DateTime; public interface Counter { @@ -14,6 +16,9 @@ public interface Counter { @NonNull T getValue(); + @Nullable + DateTime getLastUpdatedDate(); + void setValue(@NonNull T newValue) throws InvalidValueException; void increment(); diff --git a/app/src/main/java/me/tsukanov/counter/domain/IntegerCounter.java b/app/src/main/java/me/tsukanov/counter/domain/IntegerCounter.java index 0790d3ef..eaf9193a 100644 --- a/app/src/main/java/me/tsukanov/counter/domain/IntegerCounter.java +++ b/app/src/main/java/me/tsukanov/counter/domain/IntegerCounter.java @@ -2,9 +2,11 @@ import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import me.tsukanov.counter.domain.exception.CounterException; import me.tsukanov.counter.domain.exception.InvalidNameException; import me.tsukanov.counter.domain.exception.InvalidValueException; +import org.joda.time.DateTime; /** * Variation of the {@link Counter} that uses {@link Integer} type as a value. Names (identifiers) @@ -20,12 +22,14 @@ public class IntegerCounter implements Counter { private String name; private Integer value = DEFAULT_VALUE; + private @Nullable DateTime lastUpdate; public IntegerCounter(@NonNull final String name) throws CounterException { if (!isValidName(name)) { throw new InvalidNameException("Provided name is invalid"); } this.name = name; + this.lastUpdate = new DateTime(); } public IntegerCounter(@NonNull final String name, @NonNull final Integer value) @@ -42,6 +46,27 @@ public IntegerCounter(@NonNull final String name, @NonNull final Integer value) value, MIN_VALUE, MAX_VALUE)); } this.value = value; + + this.lastUpdate = null; + } + + public IntegerCounter( + @NonNull final String name, @NonNull final Integer value, @Nullable final DateTime lastUpdate) + throws CounterException { + if (!isValidName(name)) { + throw new InvalidNameException("Provided name is invalid"); + } + this.name = name; + + if (!isValidValue(value)) { + throw new InvalidValueException( + String.format( + "Desired value (%s) is outside of allowed range: %s to %s", + value, MIN_VALUE, MAX_VALUE)); + } + this.value = value; + + this.lastUpdate = lastUpdate; } @NonNull @@ -61,6 +86,12 @@ public Integer getValue() { return this.value; } + @Override + @Nullable + public DateTime getLastUpdatedDate() { + return this.lastUpdate; + } + public void setValue(@NonNull final Integer newValue) throws InvalidValueException { if (!isValidValue(newValue)) { throw new InvalidValueException( @@ -69,6 +100,7 @@ public void setValue(@NonNull final Integer newValue) throws InvalidValueExcepti newValue, MIN_VALUE, MAX_VALUE)); } this.value = newValue; + this.lastUpdate = new DateTime(); } public void increment() { diff --git a/app/src/main/java/me/tsukanov/counter/repository/SharedPrefsCounterStorage.java b/app/src/main/java/me/tsukanov/counter/repository/SharedPrefsCounterStorage.java index 82cb6176..4abbf179 100644 --- a/app/src/main/java/me/tsukanov/counter/repository/SharedPrefsCounterStorage.java +++ b/app/src/main/java/me/tsukanov/counter/repository/SharedPrefsCounterStorage.java @@ -18,6 +18,10 @@ import me.tsukanov.counter.repository.exceptions.MissingCounterException; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVPrinter; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.ISODateTimeFormat; /** * Counter storage that uses {@link SharedPreferences} as a medium. @@ -27,9 +31,15 @@ public class SharedPrefsCounterStorage implements CounterStorage { private static final String TAG = SharedPrefsCounterStorage.class.getSimpleName(); - private static final String DATA_FILE_NAME = "counters"; - private final SharedPreferences sharedPreferences; + private static final String VALUES_FILE_NAME = "counters"; + private static final String UPDATE_TIMESTAMPS_FILE_NAME = "update-timestamps"; + private static final DateTimeFormatter TIMESTAMP_FORMATTER = + ISODateTimeFormat.basicDateTimeNoMillis().withZone(DateTimeZone.getDefault()); + + private final SharedPreferences values; + private final SharedPreferences updateTimestamps; + private final BroadcastHelper broadcastHelper; private final String defaultCounterName; @@ -42,7 +52,9 @@ public SharedPrefsCounterStorage( @NonNull final Context context, @NonNull final BroadcastHelper broadcastHelper, @NonNull final String defaultCounterName) { - this.sharedPreferences = context.getSharedPreferences(DATA_FILE_NAME, Context.MODE_PRIVATE); + this.values = context.getSharedPreferences(VALUES_FILE_NAME, Context.MODE_PRIVATE); + this.updateTimestamps = + context.getSharedPreferences(UPDATE_TIMESTAMPS_FILE_NAME, Context.MODE_PRIVATE); this.broadcastHelper = broadcastHelper; this.defaultCounterName = defaultCounterName; } @@ -52,25 +64,32 @@ public SharedPrefsCounterStorage( public List readAll(boolean addDefault) { final List counters = new LinkedList<>(); - final Map dataMap = sharedPreferences.getAll(); + final Map valuesMap = values.getAll(); try { - if (dataMap.isEmpty() && addDefault) { + if (valuesMap.isEmpty() && addDefault) { final IntegerCounter defaultCounter = new IntegerCounter(this.defaultCounterName); counters.add(defaultCounter); write(defaultCounter); - } else { - for (Map.Entry entry : dataMap.entrySet()) { - counters.add(new IntegerCounter(entry.getKey(), (Integer) entry.getValue())); - } + return counters; + } + + for (Map.Entry valEntry : valuesMap.entrySet()) { + final String updateTimestampStr = updateTimestamps.getString(valEntry.getKey(), null); + final DateTime updateTimestamp = + updateTimestampStr != null + ? DateTime.parse(updateTimestampStr, TIMESTAMP_FORMATTER) + : null; + + counters.add( + new IntegerCounter(valEntry.getKey(), (Integer) valEntry.getValue(), updateTimestamp)); } + Collections.sort(counters, (x, y) -> x.getName().compareTo(y.getName())); + return counters; + } catch (CounterException e) { throw new RuntimeException(e); } - - Collections.sort(counters, (x, y) -> x.getName().compareTo(y.getName())); - - return counters; } @NonNull @@ -101,9 +120,14 @@ public IntegerCounter getFirst() { @SuppressLint("ApplySharedPref") @Override public void write(@NonNull final IntegerCounter counter) { - final SharedPreferences.Editor prefsEditor = sharedPreferences.edit(); - prefsEditor.putInt(counter.getName(), counter.getValue()); - prefsEditor.commit(); + values.edit().putInt(counter.getName(), counter.getValue()).commit(); + + if (counter.getLastUpdatedDate() != null) { + updateTimestamps + .edit() + .putString(counter.getName(), counter.getLastUpdatedDate().toString(TIMESTAMP_FORMATTER)) + .commit(); + } broadcastHelper.sendBroadcast(Actions.COUNTER_SET_CHANGE); } @@ -113,7 +137,7 @@ public void write(@NonNull final IntegerCounter counter) { public void overwriteAll(@NonNull List counters) { Log.i(TAG, String.format("Writing %s counters to storage", counters.size())); - final SharedPreferences.Editor prefsEditor = sharedPreferences.edit(); + final SharedPreferences.Editor prefsEditor = values.edit(); prefsEditor.clear(); for (IntegerCounter c : counters) { prefsEditor.putInt(c.getName(), c.getValue()); @@ -132,7 +156,7 @@ public void overwriteAll(@NonNull List counters) { @SuppressLint("ApplySharedPref") @Override public void delete(@NonNull Object counterName) { - final SharedPreferences.Editor prefsEditor = sharedPreferences.edit(); + final SharedPreferences.Editor prefsEditor = values.edit(); prefsEditor.remove(counterName.toString()); prefsEditor.commit(); @@ -142,7 +166,7 @@ public void delete(@NonNull Object counterName) { @SuppressLint("ApplySharedPref") @Override public void wipe() { - SharedPreferences.Editor prefsEditor = sharedPreferences.edit(); + SharedPreferences.Editor prefsEditor = values.edit(); prefsEditor.clear(); prefsEditor.commit(); @@ -152,14 +176,21 @@ public void wipe() { @NonNull @Override public String toCsv() throws IOException { - final StringBuilder output = new StringBuilder(); - final CSVPrinter csvPrinter = new CSVPrinter(output, CSVFormat.DEFAULT); + try (CSVPrinter csvPrinter = new CSVPrinter(output, CSVFormat.DEFAULT)) { - for (final IntegerCounter c : readAll(false)) { - csvPrinter.printRecord(c.getName(), c.getValue()); - } + csvPrinter.printRecord("Name", "Value", "Last Update"); + + for (final IntegerCounter c : readAll(false)) { + csvPrinter.printRecord( + c.getName(), + c.getValue(), + c.getLastUpdatedDate() != null + ? c.getLastUpdatedDate().toString(TIMESTAMP_FORMATTER) + : ""); + } - return output.toString(); + return output.toString(); + } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b0ce678d..34f22957 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,6 +5,7 @@ Counter + - + Last updated at {timestamp} Open navigation