diff --git a/app/build.gradle b/app/build.gradle
index 0ee5a61f5..75e13f84f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -25,7 +25,7 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
debug {
- testCoverageEnabled = true
+ testCoverageEnabled = false
}
}
diff --git a/app/src/main/java/org/isoron/uhabits/AppComponent.java b/app/src/main/java/org/isoron/uhabits/AppComponent.java
index 8c07b93ea..adf4937ea 100644
--- a/app/src/main/java/org/isoron/uhabits/AppComponent.java
+++ b/app/src/main/java/org/isoron/uhabits/AppComponent.java
@@ -29,6 +29,7 @@ import org.isoron.uhabits.models.*;
import org.isoron.uhabits.models.sqlite.*;
import org.isoron.uhabits.notifications.*;
import org.isoron.uhabits.preferences.*;
+import org.isoron.uhabits.sync.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.widgets.*;
@@ -74,6 +75,8 @@ public interface AppComponent
ReminderScheduler getReminderScheduler();
+ SyncManager getSyncManager();
+
TaskRunner getTaskRunner();
WidgetPreferences getWidgetPreferences();
diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java
index deb1b3649..6101b60e5 100644
--- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java
+++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java
@@ -25,6 +25,7 @@ import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.*;
import org.isoron.uhabits.activities.habits.list.model.*;
import org.isoron.uhabits.preferences.*;
+import org.isoron.uhabits.sync.*;
import org.isoron.uhabits.utils.*;
/**
@@ -46,6 +47,8 @@ public class ListHabitsActivity extends BaseActivity
private MidnightTimer midnightTimer;
+ private SyncManager syncManager;
+
public ListHabitsComponent getListHabitsComponent()
{
return component;
@@ -81,6 +84,7 @@ public class ListHabitsActivity extends BaseActivity
rootView.setController(controller, selectionMenu);
midnightTimer = component.getMidnightTimer();
+ syncManager = app.getComponent().getSyncManager();
setScreen(screen);
controller.onStartup();
@@ -89,6 +93,7 @@ public class ListHabitsActivity extends BaseActivity
@Override
protected void onPause()
{
+ syncManager.stopListening();
midnightTimer.onPause();
screen.onDettached();
adapter.cancelRefresh();
@@ -102,6 +107,7 @@ public class ListHabitsActivity extends BaseActivity
screen.onAttached();
rootView.postInvalidate();
midnightTimer.onResume();
+ syncManager.startListening();
if (prefs.getTheme() == ThemeSwitcher.THEME_DARK &&
prefs.isPureBlackEnabled() != pureBlack)
diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java
index 1796d1a90..aeda45bcf 100644
--- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java
+++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java
@@ -133,6 +133,7 @@ public class ListHabitsScreen extends BaseScreen
public void onCommandExecuted(@NonNull Command command,
@Nullable Long refreshKey)
{
+ if(command.isRemote()) return;
showMessage(command.getExecuteStringId());
}
diff --git a/app/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java b/app/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java
index 36dadf340..0f9ee800d 100644
--- a/app/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java
+++ b/app/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java
@@ -61,6 +61,13 @@ public class SettingsFragment extends PreferenceFragmentCompat
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
+ Context appContext = getContext().getApplicationContext();
+ if(appContext instanceof HabitsApplication)
+ {
+ HabitsApplication app = (HabitsApplication) appContext;
+ prefs = app.getComponent().getPreferences();
+ }
+
setResultOnPreferenceClick("importData", RESULT_IMPORT_DATA);
setResultOnPreferenceClick("exportCSV", RESULT_EXPORT_CSV);
setResultOnPreferenceClick("exportDB", RESULT_EXPORT_DB);
@@ -68,13 +75,21 @@ public class SettingsFragment extends PreferenceFragmentCompat
setResultOnPreferenceClick("bugReport", RESULT_BUG_REPORT);
updateRingtoneDescription();
+ updateSync();
+ }
- Context appContext = getContext().getApplicationContext();
- if(appContext instanceof HabitsApplication)
- {
- HabitsApplication app = (HabitsApplication) appContext;
- prefs = app.getComponent().getPreferences();
- }
+ private void updateSync()
+ {
+ if(prefs == null) return;
+ boolean enabled = prefs.isSyncFeatureEnabled();
+
+ Preference syncKey = findPreference("pref_sync_key");
+ syncKey.setSummary(prefs.getSyncKey());
+ syncKey.setVisible(enabled);
+
+ Preference syncAddress = findPreference("pref_sync_address");
+ syncAddress.setSummary(prefs.getSyncAddress());
+ syncAddress.setVisible(enabled);
}
@Override
@@ -127,6 +142,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
String key)
{
BackupManager.dataChanged("org.isoron.uhabits");
+ updateSync();
}
private void setResultOnPreferenceClick(String key, final int result)
diff --git a/app/src/main/java/org/isoron/uhabits/commands/Command.java b/app/src/main/java/org/isoron/uhabits/commands/Command.java
index 27794cccc..6059845e9 100644
--- a/app/src/main/java/org/isoron/uhabits/commands/Command.java
+++ b/app/src/main/java/org/isoron/uhabits/commands/Command.java
@@ -39,14 +39,18 @@ public abstract class Command
{
private String id;
+ private boolean isRemote;
+
public Command()
{
id = DatabaseUtils.getRandomId();
+ isRemote = false;
}
public Command(String id)
{
this.id = id;
+ isRemote = false;
}
public abstract void execute();
@@ -71,6 +75,16 @@ public abstract class Command
return null;
}
+ public boolean isRemote()
+ {
+ return isRemote;
+ }
+
+ public void setRemote(boolean remote)
+ {
+ isRemote = remote;
+ }
+
@NonNull
public JSONObject toJson()
{
diff --git a/app/src/main/java/org/isoron/uhabits/commands/CommandParser.java b/app/src/main/java/org/isoron/uhabits/commands/CommandParser.java
index 104e308a4..1adf190a4 100644
--- a/app/src/main/java/org/isoron/uhabits/commands/CommandParser.java
+++ b/app/src/main/java/org/isoron/uhabits/commands/CommandParser.java
@@ -26,12 +26,15 @@ import com.google.gson.*;
import org.isoron.uhabits.models.*;
import org.json.*;
+import javax.inject.*;
+
public class CommandParser
{
private HabitList habitList;
private ModelFactory modelFactory;
+ @Inject
public CommandParser(@NonNull HabitList habitList,
@NonNull ModelFactory modelFactory)
{
diff --git a/app/src/main/java/org/isoron/uhabits/commands/CreateRepetitionCommand.java b/app/src/main/java/org/isoron/uhabits/commands/CreateRepetitionCommand.java
index eff7a4149..ddebd1073 100644
--- a/app/src/main/java/org/isoron/uhabits/commands/CreateRepetitionCommand.java
+++ b/app/src/main/java/org/isoron/uhabits/commands/CreateRepetitionCommand.java
@@ -95,9 +95,9 @@ public class CreateRepetitionCommand extends Command
@NonNull
public String event = "CreateRep";
- public long habitId;
+ public long habit;
- public long timestamp;
+ public long repTimestamp;
public int value;
@@ -107,18 +107,18 @@ public class CreateRepetitionCommand extends Command
Long habitId = command.habit.getId();
if(habitId == null) throw new RuntimeException("Habit not saved");
- this.habitId = habitId;
- this.timestamp = command.timestamp;
+ this.habit = habitId;
+ this.repTimestamp = command.timestamp;
this.value = command.value;
}
public CreateRepetitionCommand toCommand(@NonNull HabitList habitList)
{
- Habit h = habitList.getById(habitId);
+ Habit h = habitList.getById(habit);
if(h == null) throw new HabitNotFoundException();
CreateRepetitionCommand command;
- command = new CreateRepetitionCommand(h, timestamp, value);
+ command = new CreateRepetitionCommand(h, repTimestamp, value);
command.setId(id);
return command;
}
diff --git a/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java b/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java
index 996a406fd..556bf3e14 100644
--- a/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java
+++ b/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java
@@ -73,9 +73,9 @@ public class ToggleRepetitionCommand extends Command
@NonNull
public String event = "Toggle";
- public long habitId;
+ public long habit;
- public long timestamp;
+ public long repTimestamp;
public Record(@NonNull ToggleRepetitionCommand command)
{
@@ -83,17 +83,17 @@ public class ToggleRepetitionCommand extends Command
Long habitId = command.habit.getId();
if(habitId == null) throw new RuntimeException("Habit not saved");
- this.timestamp = command.timestamp;
- this.habitId = habitId;
+ this.repTimestamp = command.timestamp;
+ this.habit = habitId;
}
public ToggleRepetitionCommand toCommand(@NonNull HabitList habitList)
{
- Habit h = habitList.getById(habitId);
+ Habit h = habitList.getById(habit);
if(h == null) throw new HabitNotFoundException();
ToggleRepetitionCommand command;
- command = new ToggleRepetitionCommand(h, timestamp);
+ command = new ToggleRepetitionCommand(h, repTimestamp);
command.setId(id);
return command;
}
diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java
index 5500f588d..bac4902c7 100644
--- a/app/src/main/java/org/isoron/uhabits/models/Habit.java
+++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java
@@ -28,7 +28,6 @@ import java.util.*;
import javax.inject.*;
-import static android.R.attr.*;
import static org.isoron.uhabits.models.Checkmark.*;
/**
@@ -328,7 +327,7 @@ public class Habit
public boolean isNumerical()
{
- return type == NUMBER_HABIT;
+ return data.type == NUMBER_HABIT;
}
public HabitData getData()
diff --git a/app/src/main/java/org/isoron/uhabits/preferences/Preferences.java b/app/src/main/java/org/isoron/uhabits/preferences/Preferences.java
index 3ff1b2906..560b7647a 100644
--- a/app/src/main/java/org/isoron/uhabits/preferences/Preferences.java
+++ b/app/src/main/java/org/isoron/uhabits/preferences/Preferences.java
@@ -77,6 +77,26 @@ public class Preferences
}
}
+ public String getSyncAddress()
+ {
+ return prefs.getString("pref_sync_address", "https://sync.loophabits.org:4000");
+ }
+
+ public String getSyncClientId()
+ {
+ String id = prefs.getString("pref_sync_client_id", "");
+ if(!id.isEmpty()) return id;
+
+ id = UUID.randomUUID().toString();
+ prefs.edit().putString("pref_sync_client_id", id).apply();
+ return id;
+ }
+
+ public boolean isSyncFeatureEnabled()
+ {
+ return prefs.getBoolean("pref_feature_sync", false);
+ }
+
public void setDefaultOrder(HabitList.Order order)
{
prefs.edit().putString("pref_default_order", order.name()).apply();
@@ -117,7 +137,7 @@ public class Preferences
public long getLastSync()
{
- return prefs.getLong("lastSync", 0);
+ return prefs.getLong("last_sync", 0);
}
public void setLastSync(long timestamp)
diff --git a/app/src/main/java/org/isoron/uhabits/sync/SyncManager.java b/app/src/main/java/org/isoron/uhabits/sync/SyncManager.java
index 35d536db7..f5320d430 100644
--- a/app/src/main/java/org/isoron/uhabits/sync/SyncManager.java
+++ b/app/src/main/java/org/isoron/uhabits/sync/SyncManager.java
@@ -26,7 +26,6 @@ import android.util.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.preferences.*;
-import org.isoron.uhabits.utils.*;
import org.json.*;
import java.io.*;
@@ -36,6 +35,7 @@ import java.security.cert.Certificate;
import java.security.cert.*;
import java.util.*;
+import javax.inject.*;
import javax.net.ssl.*;
import io.socket.client.*;
@@ -44,7 +44,7 @@ import io.socket.emitter.*;
import static io.socket.client.Socket.*;
-public class SyncManager
+public class SyncManager implements CommandRunner.Listener
{
public static final String EVENT_AUTH = "auth";
@@ -58,12 +58,9 @@ public class SyncManager
public static final String EVENT_POST_EVENT = "postEvent";
- public static final String SYNC_SERVER_URL =
- "https://sync.loophabits.org:4000";
+ private String clientId;
- private static String CLIENT_ID;
-
- private static String GROUP_KEY;
+ private String groupKey;
@NonNull
private Socket socket;
@@ -84,7 +81,8 @@ public class SyncManager
private CommandParser commandParser;
- public SyncManager(@NonNull Context context,
+ @Inject
+ public SyncManager(@AppContext @NonNull Context context,
@NonNull Preferences prefs,
@NonNull CommandRunner commandRunner,
@NonNull CommandParser commandParser)
@@ -97,15 +95,16 @@ public class SyncManager
pendingConfirmation = new LinkedList<>();
pendingEmit = Event.getAll();
- GROUP_KEY = prefs.getSyncKey();
- CLIENT_ID = DatabaseUtils.getRandomId();
+ groupKey = prefs.getSyncKey();
+ clientId = prefs.getSyncClientId();
+ String serverURL = prefs.getSyncAddress();
- Log.d("SyncManager", CLIENT_ID);
+ Log.d("SyncManager", clientId);
try
{
IO.setDefaultSSLContext(getCACertSSLContext());
- socket = IO.socket(SYNC_SERVER_URL);
+ socket = IO.socket(serverURL);
logSocketEvent(socket, EVENT_CONNECT, "Connected");
logSocketEvent(socket, EVENT_CONNECT_TIMEOUT, "Connect timeout");
logSocketEvent(socket, EVENT_CONNECTING, "Connecting...");
@@ -124,8 +123,6 @@ public class SyncManager
socket.on(EVENT_EXECUTE_EVENT, new OnExecuteCommandListener());
socket.on(EVENT_AUTH_OK, new OnAuthOKListener());
socket.on(EVENT_FETCH_OK, new OnFetchOKListener());
-
- socket.connect();
}
catch (URISyntaxException e)
{
@@ -133,14 +130,12 @@ public class SyncManager
}
}
- public void close()
+ @Override
+ public void onCommandExecuted(@NonNull Command command,
+ @Nullable Long refreshKey)
{
- socket.off();
- socket.close();
- }
+ if(command.isRemote()) return;
- public void postCommand(Command command)
- {
JSONObject msg = command.toJson();
Long now = new Date().getTime();
Event e = new Event(command.getId(), now, msg.toString());
@@ -152,6 +147,21 @@ public class SyncManager
if (readyToEmit) emitPending();
}
+ public void startListening()
+ {
+ if(!prefs.isSyncFeatureEnabled()) return;
+ if(groupKey.isEmpty()) return;
+
+ socket.connect();
+ commandRunner.addListener(this);
+ }
+
+ public void stopListening()
+ {
+ commandRunner.removeListener(this);
+ socket.close();
+ }
+
private void emitPending()
{
try
@@ -200,7 +210,13 @@ public class SyncManager
private void logSocketEvent(Socket socket, String event, final String msg)
{
- socket.on(event, args -> Log.i("SyncManager", msg));
+ socket.on(event, args ->
+ {
+ Log.i("SyncManager", msg);
+ for (Object o : args)
+ if (o instanceof SocketIOException)
+ ((SocketIOException) o).printStackTrace();
+ });
}
private void updateLastSync(Long timestamp)
@@ -249,8 +265,8 @@ public class SyncManager
try
{
JSONObject json = new JSONObject();
- json.put("groupKey", GROUP_KEY);
- json.put("clientId", CLIENT_ID);
+ json.put("groupKey", groupKey);
+ json.put("clientId", clientId);
json.put("version", BuildConfig.VERSION_NAME);
return json;
}
@@ -292,6 +308,8 @@ public class SyncManager
private void executeCommand(JSONObject root) throws JSONException
{
Command received = commandParser.parse(root);
+ received.setRemote(true);
+
for (Event e : pendingConfirmation)
{
if (e.serverId.equals(received.getId()))
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 7acafd361..62a20f148 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -147,10 +147,18 @@
android:key="pref_feature_numerical_habits"
android:title="Enable numerical habits"/>
+
+
+
+
+ android:title="Sync key"/>