diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt index eac956516..9bac43d59 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt @@ -97,9 +97,9 @@ class ListHabitsScreen commandRunner.removeListener(this) } - override fun onCommandExecuted(command: Command, refreshKey: Long?) { - if (command.isRemote) return - showMessage(getExecuteString(command)) + override fun onCommandExecuted(command: Command?, refreshKey: Long?) { + if (command != null) + showMessage(getExecuteString(command)) } override fun onResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/tasks/ImportDataTask.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/tasks/ImportDataTask.java index 6740e2ab4..db56a3857 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/tasks/ImportDataTask.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/tasks/ImportDataTask.java @@ -19,6 +19,8 @@ package org.isoron.uhabits.tasks; +import android.util.*; + import androidx.annotation.NonNull; import com.google.auto.factory.*; @@ -83,7 +85,7 @@ public class ImportDataTask implements Task catch (Exception e) { result = FAILED; - e.printStackTrace(); + Log.e("ImportDataTask", "Import failed", e); } modelFactory.db.endTransaction(); diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/WidgetUpdater.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/WidgetUpdater.kt index 81e3619ab..c28cfa68f 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/WidgetUpdater.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/WidgetUpdater.kt @@ -45,7 +45,7 @@ class WidgetUpdater private var lastUpdated = 0L - override fun onCommandExecuted(command: Command, refreshKey: Long?) { + override fun onCommandExecuted(command: Command?, refreshKey: Long?) { updateWidgets(refreshKey) } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/Config.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/Config.java index 286755a33..1a5383cee 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/Config.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/Config.java @@ -22,5 +22,5 @@ package org.isoron.uhabits.core; public class Config { public static final String DATABASE_FILENAME = "uhabits.db"; - public static int DATABASE_VERSION = 23; + public static int DATABASE_VERSION = 24; } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/Command.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/Command.java index f8d7feb16..37f1d9254 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/Command.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/Command.java @@ -38,18 +38,14 @@ public abstract class Command { private String id; - private boolean isRemote; - public Command() { id = StringUtils.getRandomId(); - isRemote = false; } public Command(String id) { this.id = id; - isRemote = false; } public abstract void execute(); @@ -64,16 +60,6 @@ public abstract class Command this.id = id; } - public boolean isRemote() - { - return isRemote; - } - - public void setRemote(boolean remote) - { - isRemote = remote; - } - @NonNull public String toJson() { diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/CommandRunner.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/CommandRunner.java index a70500c87..5e46ebe32 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/CommandRunner.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/CommandRunner.java @@ -66,12 +66,17 @@ public class CommandRunner @Override public void onPostExecute() { - for (Listener l : listeners) - l.onCommandExecuted(command, refreshKey); + notifyListeners(command, refreshKey); } }); } + public void notifyListeners(Command command, Long refreshKey) + { + for (Listener l : listeners) + l.onCommandExecuted(command, refreshKey); + } + public void removeListener(Listener l) { listeners.remove(l); @@ -83,7 +88,7 @@ public class CommandRunner */ public interface Listener { - void onCommandExecuted(@NonNull Command command, + void onCommandExecuted(@Nullable Command command, @Nullable Long refreshKey); } } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/io/LoopDBImporter.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/io/LoopDBImporter.java index 329e88681..59aec5010 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/io/LoopDBImporter.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/io/LoopDBImporter.java @@ -101,35 +101,42 @@ public class LoopDBImporter extends AbstractImporter habitsRepository = new Repository<>(HabitRecord.class, db); repsRepository = new Repository<>(RepetitionRecord.class, db); - for (HabitRecord habitRecord : habitsRepository.findAll( - "order by position")) + List records = habitsRepository.findAll("order by position"); + for (HabitRecord habitRecord : records) { - Habit habit = habitList.getById(habitRecord.id); + List reps = + repsRepository.findAll("where habit = ?", + habitRecord.id.toString()); + + Habit habit = habitList.getByUUID(habitRecord.uuid); if (habit == null) { habit = modelFactory.buildHabit(); + habitRecord.id = null; habitRecord.copyTo(habit); - runner.execute(new CreateHabitCommand(modelFactory, habitList, habit), null); + new CreateHabitCommand(modelFactory, habitList, habit).execute(); } else { Habit modified = modelFactory.buildHabit(); + habitRecord.id = habit.id; habitRecord.copyTo(modified); if (!modified.getData().equals(habit.getData())) - runner.execute(new EditHabitCommand(modelFactory, habitList, habit, modified), null); + new EditHabitCommand(modelFactory, habitList, habit, modified).execute(); } - List reps = - repsRepository.findAll("where habit = ?", - habitRecord.id.toString()); + // Reload saved version of the habit + habit = habitList.getByUUID(habitRecord.uuid); for (RepetitionRecord r : reps) { Timestamp t = new Timestamp(r.timestamp); Repetition rep = habit.getRepetitions().getByTimestamp(t); if(rep == null || rep.getValue() != r.value) - runner.execute(new CreateRepetitionCommand(habitList, habit, t, r.value), habit.id); + new CreateRepetitionCommand(habitList, habit, t, r.value).execute(); } } + + runner.notifyListeners(null, null); } } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.java index 6d2a555cd..2eef8245e 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.java @@ -356,14 +356,27 @@ public class Habit } @NonNull - public String getQuestion() { + public String getQuestion() + { return data.question; } - public void setQuestion(@NonNull String question) { + public void setQuestion(@NonNull String question) + { data.question = question; } + @NonNull + public String getUUID() + { + return data.uuid; + } + + public void setUUID(@NonNull String uuid) + { + data.uuid = uuid; + } + public static final class HabitData { @NonNull @@ -388,6 +401,8 @@ public class Habit public int type; + public String uuid; + @NonNull public String unit; @@ -409,6 +424,7 @@ public class Habit this.targetValue = 100; this.unit = ""; this.position = 0; + this.uuid = UUID.randomUUID().toString().replace("-", ""); } public HabitData(@NonNull HabitData model) @@ -425,6 +441,7 @@ public class Habit this.unit = model.unit; this.reminder = model.reminder; this.position = model.position; + this.uuid = model.uuid; } @Override @@ -443,6 +460,7 @@ public class Habit .append("reminder", reminder) .append("position", position) .append("question", question) + .append("uuid", uuid) .toString(); } @@ -468,6 +486,7 @@ public class Habit .append(reminder, habitData.reminder) .append(position, habitData.position) .append(question, habitData.question) + .append(uuid, habitData.uuid) .isEquals(); } @@ -487,6 +506,7 @@ public class Habit .append(reminder) .append(position) .append(question) + .append(uuid) .toHashCode(); } } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/HabitList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/HabitList.java index 8f19258d0..47b3a1cef 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/HabitList.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/HabitList.java @@ -83,6 +83,15 @@ public abstract class HabitList implements Iterable @Nullable public abstract Habit getById(long id); + /** + * Returns the habit with specified UUID. + * + * @param uuid the UUID of the habit + * @return the habit, or null if none exist + */ + @Nullable + public abstract Habit getByUUID(String uuid); + /** * Returns the habit that occupies a certain position. * diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java index 84716e771..503d5278d 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java @@ -94,6 +94,13 @@ public class MemoryHabitList extends HabitList return null; } + @Override + public synchronized Habit getByUUID(String uuid) + { + for (Habit h : list) if (h.getUUID().equals(uuid)) return h; + return null; + } + @NonNull @Override public synchronized Habit getByPosition(int position) diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/SQLiteHabitList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/SQLiteHabitList.java index 1ba429bfd..f940c436a 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/SQLiteHabitList.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/SQLiteHabitList.java @@ -98,6 +98,14 @@ public class SQLiteHabitList extends HabitList return list.getById(id); } + @Override + @Nullable + public synchronized Habit getByUUID(String uuid) + { + loadRecords(); + return list.getByUUID(uuid); + } + @Override @NonNull public synchronized Habit getByPosition(int position) diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecord.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecord.java index 523da0542..93f9a331b 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecord.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecord.java @@ -81,6 +81,9 @@ public class HabitRecord @Column public Long id; + @Column + public String uuid; + public void copyFrom(Habit model) { this.id = model.getId(); @@ -95,6 +98,7 @@ public class HabitRecord this.unit = model.getUnit(); this.position = model.getPosition(); this.question = model.getQuestion(); + this.uuid = model.getUUID(); Frequency freq = model.getFrequency(); this.freqNum = freq.getNumerator(); @@ -126,6 +130,7 @@ public class HabitRecord habit.setTargetValue(this.targetValue); habit.setUnit(this.unit); habit.setPosition(this.position); + habit.setUUID(this.uuid); if (reminderHour != null && reminderMin != null) { diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java index 35fa3140b..89f2b7154 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java @@ -56,7 +56,7 @@ public class ReminderScheduler implements CommandRunner.Listener } @Override - public synchronized void onCommandExecuted(@NonNull Command command, + public synchronized void onCommandExecuted(@Nullable Command command, @Nullable Long refreshKey) { if (command instanceof CreateRepetitionCommand) return; diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/NotificationTray.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/NotificationTray.java index 4ada13756..4adb888a8 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/NotificationTray.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/NotificationTray.java @@ -73,7 +73,7 @@ public class NotificationTray } @Override - public void onCommandExecuted(@NonNull Command command, + public void onCommandExecuted(@Nullable Command command, @Nullable Long refreshKey) { if (command instanceof CreateRepetitionCommand) diff --git a/android/uhabits-core/src/main/resources/migrations/24.sql b/android/uhabits-core/src/main/resources/migrations/24.sql new file mode 100644 index 000000000..a8c13d6c8 --- /dev/null +++ b/android/uhabits-core/src/main/resources/migrations/24.sql @@ -0,0 +1,2 @@ +alter table habits add column uuid text; +update habits set uuid = lower(hex(randomblob(16) || id)); \ No newline at end of file diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/BaseUnitTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/BaseUnitTest.java index c377d3187..7e9d6e813 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/BaseUnitTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/BaseUnitTest.java @@ -127,7 +127,7 @@ public class BaseUnitTest DriverManager.getConnection("jdbc:sqlite::memory:")); db.execute("pragma user_version=8;"); MigrationHelper helper = new MigrationHelper(db); - helper.migrateTo(23); + helper.migrateTo(Config.DATABASE_VERSION); return db; } catch (SQLException e) diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/io/ImportTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/io/ImportTest.java index 1fa3ba513..8c95ff082 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/io/ImportTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/io/ImportTest.java @@ -132,7 +132,7 @@ public class ImportTest extends BaseUnitTest assertTrue(file.canRead()); GenericImporter importer = new GenericImporter(habitList, - new LoopDBImporter(habitList, modelFactory, databaseOpener), + new LoopDBImporter(habitList, modelFactory, databaseOpener, commandRunner), new RewireDBImporter(habitList, modelFactory, databaseOpener), new TickmateDBImporter(habitList, modelFactory, databaseOpener), new HabitBullCSVImporter(habitList, modelFactory)); diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/HabitTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/HabitTest.java index e99e954c0..7cab255bc 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/HabitTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/HabitTest.java @@ -148,6 +148,7 @@ public class HabitTest extends BaseUnitTest public void testToString() throws Exception { Habit h = modelFactory.buildHabit(); + h.setUUID("nnnn"); h.setReminder(new Reminder(22, 30, WeekdayList.EVERY_DAY)); String expected = "{id: , data: {name: , description: ," + " frequency: {numerator: 3, denominator: 7}," + @@ -155,7 +156,7 @@ public class HabitTest extends BaseUnitTest " targetValue: 100.0, type: 0, unit: ," + " reminder: {hour: 22, minute: 30," + " days: {weekdays: [true,true,true,true,true,true,true]}}," + - " position: 0, question: }}"; + " position: 0, question: , uuid: nnnn}}"; assertThat(h.toString(), equalTo(expected)); }