Move ListHabitsBehavior to uhabits-core

pull/87/merge
Alinson S. Xavier 8 years ago
parent 95385fa8f4
commit 3e558be4d4

@ -26,6 +26,18 @@ import org.isoron.uhabits.tasks.*;
import dagger.*;
@AppScope
@Component(modules = {
AppModule.class,
HabitsModule.class,
SingleThreadModule.class,
SQLModelFactory.class
})
public interface AndroidTestComponent extends HabitsComponent
{
}
@Module
class SingleThreadModule
{
@ -36,11 +48,3 @@ class SingleThreadModule
return new SingleThreadTaskRunner();
}
}
@AppScope
@Component(modules = {
AppModule.class, SingleThreadModule.class, SQLModelFactory.class
})
public interface AndroidTestComponent extends AppComponent
{
}

@ -1,129 +0,0 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.habits.list;
import org.isoron.androidbase.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.habits.list.model.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.preferences.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.tasks.android.*;
import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.widgets.*;
import org.junit.*;
import static org.mockito.Mockito.*;
public class ListHabitsControllerTest extends BaseAndroidTest
{
private ListHabitsController controller;
private ImportDataTaskFactory importTaskFactory;
private BaseSystem system;
private CommandRunner commandRunner;
private HabitCardListAdapter adapter;
private ListHabitsScreen screen;
private AndroidPreferences prefs;
private ReminderScheduler reminderScheduler;
private SingleThreadTaskRunner taskRunner;
private WidgetUpdater widgetUpdater;
private ExportCSVTaskFactory exportCSVFactory;
private ExportDBTaskFactory exportDBFactory;
@Override
public void setUp()
{
super.setUp();
habitList = mock(HabitList.class);
system = mock(BaseSystem.class);
commandRunner = mock(CommandRunner.class);
adapter = mock(HabitCardListAdapter.class);
screen = mock(ListHabitsScreen.class);
prefs = mock(AndroidPreferences.class);
reminderScheduler = mock(ReminderScheduler.class);
taskRunner = new SingleThreadTaskRunner();
widgetUpdater = mock(WidgetUpdater.class);
importTaskFactory = mock(ImportDataTaskFactory.class);
exportCSVFactory = mock(ExportCSVTaskFactory.class);
exportDBFactory = mock(ExportDBTaskFactory.class);
controller =
spy(new ListHabitsController(system, commandRunner, habitList,
adapter, screen, prefs, reminderScheduler, taskRunner,
widgetUpdater, importTaskFactory, exportCSVFactory, exportDBFactory));
}
@Test
public void testOnHabitClick()
{
Habit h = mock(Habit.class);
controller.onHabitClick(h);
verify(screen).showHabitScreen(h);
}
@Test
public void testOnHabitReorder()
{
Habit from = mock(Habit.class);
Habit to = mock(Habit.class);
controller.onHabitReorder(from, to);
verify(habitList).reorder(from, to);
}
@Test
public void onInvalidToggle()
{
controller.onInvalidToggle();
verify(screen).showMessage(R.string.long_press_to_toggle);
}
@Test
public void onStartup_notFirstLaunch()
{
when(prefs.isFirstRun()).thenReturn(false);
controller.onStartup();
verify(prefs).incrementLaunchCount();
}
@Test
public void onStartup_firstLaunch()
{
long today = DateUtils.getStartOfToday();
when(prefs.isFirstRun()).thenReturn(true);
controller.onStartup();
verify(prefs).setFirstRun(false);
verify(prefs).updateLastHint(-1, today);
verify(screen).showIntroScreen();
}
}

@ -28,6 +28,8 @@ import android.view.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.ui.habits.list.*;
import org.isoron.uhabits.ui.habits.show.*;
import org.isoron.uhabits.utils.*;
import java.io.*;
@ -45,7 +47,9 @@ import javax.inject.*;
* permissions.
*/
@AppScope
public class BaseSystem implements CACertSSLContextProvider
public class BaseSystem implements CACertSSLContextProvider,
ListHabitsBehavior.System,
ShowHabitMenuBehavior.System
{
private Context context;
@ -84,24 +88,32 @@ public class BaseSystem implements CACertSSLContextProvider
* @return the generated file.
* @throws IOException when I/O errors occur.
*/
@Override
@NonNull
public File dumpBugReportToFile() throws IOException
public void dumpBugReportToFile()
{
try
{
String date =
DateFormats.getBackupDateFormat().format(DateUtils.getLocalTime());
String date = DateFormats
.getBackupDateFormat()
.format(DateUtils.getLocalTime());
if (context == null) throw new IllegalStateException();
if (context == null) throw new RuntimeException(
"application context should not be null");
File dir = getFilesDir("Logs");
if (dir == null) throw new IOException("log dir should not be null");
if (dir == null)
throw new IOException("log dir should not be null");
File logFile =
new File(String.format("%s/Log %s.txt", dir.getPath(), date));
FileWriter output = new FileWriter(logFile);
output.write(getBugReport());
output.close();
return logFile;
}
catch (IOException e)
{
e.printStackTrace();
}
}
/**
@ -112,6 +124,7 @@ public class BaseSystem implements CACertSSLContextProvider
* @return a String containing the bug report.
* @throws IOException when any I/O error occur.
*/
@Override
@NonNull
public String getBugReport() throws IOException
{
@ -125,6 +138,12 @@ public class BaseSystem implements CACertSSLContextProvider
return log;
}
@Override
public File getCSVOutputDir()
{
return getFilesDir("CSV");
}
public String getLogcat() throws IOException
{
int maxLineCount = 250;

@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
@ -17,17 +17,17 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities;
package org.isoron.androidbase.activities;
import org.isoron.androidbase.activities.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.*;
import org.isoron.uhabits.activities.common.dialogs.*;
import dagger.*;
@ActivityScope
@Component(modules = { ActivityModule.class },
dependencies = { AppComponent.class })
dependencies = { HabitsComponent.class })
public interface ActivityComponent
{
BaseActivity getActivity();

@ -126,7 +126,7 @@ abstract public class BaseActivity extends AppCompatActivity
component = DaggerActivityComponent
.builder()
.activityModule(new ActivityModule(this))
.appComponent(app.getComponent())
.habitsComponent(app.getComponent())
.build();
component.getThemeSwitcher().apply();

@ -41,7 +41,7 @@ public class HabitsApplication extends Application
{
private Context context;
private static AppComponent component;
private static HabitsComponent component;
private WidgetUpdater widgetUpdater;
@ -49,12 +49,12 @@ public class HabitsApplication extends Application
private NotificationTray notificationTray;
public AppComponent getComponent()
public HabitsComponent getComponent()
{
return component;
}
public static void setComponent(AppComponent component)
public static void setComponent(HabitsComponent component)
{
HabitsApplication.component = component;
}
@ -78,7 +78,7 @@ public class HabitsApplication extends Application
super.onCreate();
context = this;
component = DaggerAppComponent
component = DaggerHabitsComponent
.builder()
.appModule(new AppModule(context))
.build();

@ -40,10 +40,15 @@ import dagger.*;
@AppScope
@Component(modules = {
AppModule.class, AndroidTaskRunner.class, SQLModelFactory.class
AppModule.class,
HabitsModule.class,
AndroidTaskRunner.class,
SQLModelFactory.class
})
public interface AppComponent
public interface HabitsComponent
{
AndroidPreferences getPreferences();
BaseSystem getBaseSystem();
CommandRunner getCommandRunner();
@ -67,13 +72,15 @@ public interface AppComponent
IntentParser getIntentParser();
MidnightTimer getMidnightTimer();
ModelFactory getModelFactory();
NotificationTray getNotificationTray();
PendingIntentFactory getPendingIntentFactory();
AndroidPreferences getPreferences();
Preferences getCorePreferences();
ReminderScheduler getReminderScheduler();
@ -86,6 +93,4 @@ public interface AppComponent
WidgetPreferences getWidgetPreferences();
WidgetUpdater getWidgetUpdater();
MidnightTimer getMidnightTimer();
}

@ -0,0 +1,35 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits;
import org.isoron.uhabits.preferences.*;
import dagger.*;
@Module
public class HabitsModule
{
@Provides
@AppScope
public static Preferences getPreferences(AndroidPreferences preferences)
{
return preferences;
}
}

@ -36,7 +36,7 @@ public class AboutActivity extends BaseActivity
{
super.onCreate(savedInstanceState);
HabitsApplication app = (HabitsApplication) getApplication();
AppComponent cmp = app.getComponent();
HabitsComponent cmp = app.getComponent();
AboutScreen screen = new AboutScreen(this, cmp.getIntentFactory());
AboutBehavior behavior = new AboutBehavior(cmp.getPreferences(), screen);
AboutRootView rootView = new AboutRootView(this, behavior);

@ -55,7 +55,7 @@ public class EditHabitDialog extends AppCompatDialogFragment
protected HabitList habitList;
protected AppComponent component;
protected HabitsComponent component;
protected ModelFactory modelFactory;

@ -64,8 +64,8 @@ public class ListHabitsActivity extends BaseActivity
component = DaggerListHabitsComponent
.builder()
.appComponent(app.getComponent())
.activityModule(new ActivityModule(this))
.habitsComponent(app.getComponent())
.listHabitsModule(new ListHabitsModule(this))
.build();
ListHabitsMenu menu = component.getMenu();

@ -23,13 +23,12 @@ import org.isoron.androidbase.activities.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.habits.list.controllers.*;
import org.isoron.uhabits.activities.habits.list.model.*;
import org.isoron.uhabits.utils.*;
import dagger.*;
@ActivityScope
@Component(modules = { ActivityModule.class },
dependencies = { AppComponent.class })
@Component(modules = { ListHabitsModule.class },
dependencies = { HabitsComponent.class })
public interface ListHabitsComponent
{
HabitCardListAdapter getAdapter();

@ -21,21 +21,16 @@ package org.isoron.uhabits.activities.habits.list;
import android.support.annotation.*;
import org.isoron.androidbase.*;
import org.isoron.androidbase.activities.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.habits.list.controllers.*;
import org.isoron.uhabits.activities.habits.list.model.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.preferences.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.tasks.android.*;
import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.widgets.*;
import org.isoron.uhabits.ui.habits.list.*;
import java.io.*;
import java.util.*;
import javax.inject.*;
@ -43,82 +38,53 @@ import javax.inject.*;
public class ListHabitsController
implements HabitCardListController.HabitListener
{
@NonNull
private final ListHabitsScreen screen;
@NonNull
private final BaseSystem system;
private final ListHabitsBehavior behavior;
@NonNull
private final HabitList habitList;
private final ListHabitsScreen screen;
@NonNull
private final HabitCardListAdapter adapter;
@NonNull
private final AndroidPreferences prefs;
@NonNull
private final CommandRunner commandRunner;
@NonNull
private final TaskRunner taskRunner;
private ReminderScheduler reminderScheduler;
private WidgetUpdater widgetUpdater;
private ImportDataTaskFactory importTaskFactory;
private ExportCSVTaskFactory exportCSVFactory;
private ExportDBTaskFactory exportDBFactory;
@Inject
public ListHabitsController(@NonNull BaseSystem system,
@NonNull CommandRunner commandRunner,
@NonNull HabitList habitList,
public ListHabitsController(@NonNull ListHabitsBehavior behavior,
@NonNull HabitCardListAdapter adapter,
@NonNull ListHabitsScreen screen,
@NonNull AndroidPreferences prefs,
@NonNull ReminderScheduler reminderScheduler,
@NonNull TaskRunner taskRunner,
@NonNull WidgetUpdater widgetUpdater,
@NonNull ImportDataTaskFactory importTaskFactory,
@NonNull ExportCSVTaskFactory exportCSVFactory,
@NonNull ExportDBTaskFactory exportDBFactory)
{
this.behavior = behavior;
this.adapter = adapter;
this.commandRunner = commandRunner;
this.habitList = habitList;
this.prefs = prefs;
this.screen = screen;
this.system = system;
this.taskRunner = taskRunner;
this.reminderScheduler = reminderScheduler;
this.widgetUpdater = widgetUpdater;
this.importTaskFactory = importTaskFactory;
this.exportCSVFactory = exportCSVFactory;
this.exportDBFactory = exportDBFactory;
}
public void onExportCSV()
@Override
public void onEdit(@NonNull Habit habit, long timestamp)
{
List<Habit> selected = new LinkedList<>();
for (Habit h : habitList) selected.add(h);
File outputDir = system.getFilesDir("CSV");
behavior.onEdit(habit, timestamp);
}
taskRunner.execute(
exportCSVFactory.create(selected, outputDir, filename -> {
if (filename != null) screen.showSendFileScreen(filename);
else screen.showMessage(R.string.could_not_export);
}));
public void onExportCSV()
{
behavior.onExportCSV();
}
public void onExportDB()
{
taskRunner.execute(exportDBFactory.create(filename -> {
taskRunner.execute(exportDBFactory.create(filename ->
{
if (filename != null) screen.showSendFileScreen(filename);
else screen.showMessage(R.string.could_not_export);
}));
@ -127,19 +93,20 @@ public class ListHabitsController
@Override
public void onHabitClick(@NonNull Habit h)
{
screen.showHabitScreen(h);
behavior.onClickHabit(h);
}
@Override
public void onHabitReorder(@NonNull Habit from, @NonNull Habit to)
{
taskRunner.execute(() -> habitList.reorder(from, to));
behavior.onReorderHabit(from, to);
}
public void onImportData(@NonNull File file,
@NonNull OnFinishedListener finishedListener)
{
taskRunner.execute(importTaskFactory.create(file, result -> {
taskRunner.execute(importTaskFactory.create(file, result ->
{
switch (result)
{
case ImportDataTask.SUCCESS:
@ -166,21 +133,6 @@ public class ListHabitsController
screen.showMessage(R.string.long_press_to_edit);
}
@Override
public void onEdit(@NonNull Habit habit, long timestamp)
{
CheckmarkList checkmarks = habit.getCheckmarks();
double oldValue = checkmarks.getValues(timestamp, timestamp)[0];
screen.showNumberPicker(oldValue / 1000, habit.getUnit(), newValue -> {
newValue = Math.round(newValue * 1000);
commandRunner.execute(
new CreateRepetitionCommand(habit, timestamp, (int) newValue),
habit.getId());
});
}
@Override
public void onInvalidToggle()
{
@ -189,55 +141,23 @@ public class ListHabitsController
public void onRepairDB()
{
taskRunner.execute(() -> {
habitList.repair();
screen.showMessage(R.string.database_repaired);
});
behavior.onRepairDB();
}
public void onSendBugReport()
{
try
{
system.dumpBugReportToFile();
}
catch (IOException e)
{
// ignored
}
try
{
String log = system.getBugReport();
int to = R.string.bugReportTo;
int subject = R.string.bugReportSubject;
screen.showSendEmailScreen(to, subject, log);
}
catch (IOException e)
{
e.printStackTrace();
screen.showMessage(R.string.bug_report_failed);
}
behavior.onSendBugReport();
}
public void onStartup()
{
prefs.incrementLaunchCount();
if (prefs.isFirstRun()) onFirstRun();
behavior.onStartup();
}
@Override
public void onToggle(@NonNull Habit habit, long timestamp)
{
commandRunner.execute(new ToggleRepetitionCommand(habit, timestamp),
habit.getId());
}
private void onFirstRun()
{
prefs.setFirstRun(false);
prefs.updateLastHint(-1, DateUtils.getStartOfToday());
screen.showIntroScreen();
behavior.onToggle(habit, timestamp);
}
public interface OnFinishedListener

@ -0,0 +1,48 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.habits.list;
import org.isoron.androidbase.*;
import org.isoron.androidbase.activities.*;
import org.isoron.uhabits.ui.habits.list.*;
import dagger.*;
@Module
public class ListHabitsModule extends ActivityModule
{
public ListHabitsModule(BaseActivity activity)
{
super(activity);
}
@Provides
ListHabitsBehavior.Screen getScreen(ListHabitsScreen screen)
{
return screen;
}
@Provides
ListHabitsBehavior.System getSystem(BaseSystem system)
{
return system;
}
}

@ -39,6 +39,7 @@ import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.preferences.*;
import org.isoron.uhabits.ui.habits.list.*;
import org.isoron.uhabits.utils.*;
import java.io.*;
@ -51,7 +52,7 @@ import static android.view.inputmethod.EditorInfo.*;
@ActivityScope
public class ListHabitsScreen extends BaseScreen
implements CommandRunner.Listener
implements CommandRunner.Listener, ListHabitsBehavior.Screen
{
public static final int REQUEST_OPEN_DOCUMENT = 6;
@ -100,9 +101,12 @@ public class ListHabitsScreen extends BaseScreen
@NonNull ListHabitsRootView rootView,
@NonNull IntentFactory intentFactory,
@NonNull ThemeSwitcher themeSwitcher,
@NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory,
@NonNull ColorPickerDialogFactory colorPickerFactory,
@NonNull EditHabitDialogFactory editHabitDialogFactory,
@NonNull
ConfirmDeleteDialogFactory confirmDeleteDialogFactory,
@NonNull
ColorPickerDialogFactory colorPickerFactory,
@NonNull
EditHabitDialogFactory editHabitDialogFactory,
@NonNull AndroidPreferences prefs,
@NonNull CommandParser commandParser)
{
@ -127,7 +131,7 @@ public class ListHabitsScreen extends BaseScreen
public void onCommandExecuted(@NonNull Command command,
@Nullable Long refreshKey)
{
if(command.isRemote()) return;
if (command.isRemote()) return;
showMessage(commandParser.getExecuteString(command));
}
@ -172,9 +176,16 @@ public class ListHabitsScreen extends BaseScreen
activity.showDialog(picker, "picker");
}
public void showCreateBooleanHabitScreen()
{
EditHabitDialog dialog;
dialog = editHabitDialogFactory.createBoolean();
activity.showDialog(dialog, "editHabit");
}
public void showCreateHabitScreen()
{
if(!prefs.isNumericalHabitsFeatureEnabled())
if (!prefs.isNumericalHabitsFeatureEnabled())
{
showCreateBooleanHabitScreen();
return;
@ -182,8 +193,9 @@ public class ListHabitsScreen extends BaseScreen
Dialog dialog = new AlertDialog.Builder(activity)
.setTitle("Type of habit")
.setItems(R.array.habitTypes, (d, which) -> {
if(which == 0) showCreateBooleanHabitScreen();
.setItems(R.array.habitTypes, (d, which) ->
{
if (which == 0) showCreateBooleanHabitScreen();
else showCreateNumericalHabitScreen();
})
.create();
@ -191,20 +203,6 @@ public class ListHabitsScreen extends BaseScreen
dialog.show();
}
private void showCreateNumericalHabitScreen()
{
EditHabitDialog dialog;
dialog = editHabitDialogFactory.createNumerical();
activity.showDialog(dialog, "editHabit");
}
public void showCreateBooleanHabitScreen()
{
EditHabitDialog dialog;
dialog = editHabitDialogFactory.createBoolean();
activity.showDialog(dialog, "editHabit");
}
public void showDeleteConfirmationScreen(ConfirmDeleteDialog.Callback callback)
{
activity.showDialog(confirmDeleteDialogFactory.create(callback));
@ -223,6 +221,7 @@ public class ListHabitsScreen extends BaseScreen
activity.startActivity(intent);
}
@Override
public void showHabitScreen(@NonNull Habit habit)
{
Intent intent = intentFactory.startShowHabitActivity(activity, habit);
@ -235,15 +234,48 @@ public class ListHabitsScreen extends BaseScreen
activity.startActivityForResult(intent, REQUEST_OPEN_DOCUMENT);
}
@Override
public void showIntroScreen()
{
Intent intent = intentFactory.startIntroActivity(activity);
activity.startActivity(intent);
}
@Override
public void showMessage(@NonNull ListHabitsBehavior.Message m)
{
switch (m)
{
case COULD_NOT_EXPORT:
showMessage(R.string.could_not_export);
break;
case IMPORT_SUCCESSFUL:
showMessage(R.string.habits_imported);
break;
case IMPORT_FAILED:
showMessage(R.string.could_not_import);
break;
case DATABASE_REPAIRED:
showMessage(R.string.database_repaired);
break;
case COULD_NOT_GENERATE_BUG_REPORT:
showMessage(R.string.bug_report_failed);
break;
case FILE_NOT_RECOGNIZED:
showMessage(R.string.file_not_recognized);
break;
}
}
@Override
public void showNumberPicker(double value,
@NonNull String unit,
@NonNull NumberPickerCallback callback)
@NonNull ListHabitsBehavior.NumberPickerCallback callback)
{
LayoutInflater inflater = activity.getLayoutInflater();
View view = inflater.inflate(R.layout.number_picker_dialog, null);
@ -292,21 +324,12 @@ public class ListHabitsScreen extends BaseScreen
dialog.show();
}
private void refreshInitialValue(NumberPicker picker2)
{
// Workaround for a bug on Android:
// https://code.google.com/p/android/issues/detail?id=35482
try
{
Field f = NumberPicker.class.getDeclaredField("mInputText");
f.setAccessible(true);
EditText inputText = (EditText) f.get(picker2);
inputText.setFilters(new InputFilter[0]);
}
catch (Exception e)
@Override
public void showSendBugReportToDeveloperScreen(String log)
{
throw new RuntimeException(e);
}
int to = R.string.bugReportTo;
int subject = R.string.bugReportSubject;
showSendEmailScreen(to, subject, log);
}
public void showSettingsScreen()
@ -373,8 +396,27 @@ public class ListHabitsScreen extends BaseScreen
}
}
public interface NumberPickerCallback
private void refreshInitialValue(NumberPicker picker2)
{
void onNumberPicked(double newValue);
// Workaround for a bug on Android:
// https://code.google.com/p/android/issues/detail?id=35482
try
{
Field f = NumberPicker.class.getDeclaredField("mInputText");
f.setAccessible(true);
EditText inputText = (EditText) f.get(picker2);
inputText.setFilters(new InputFilter[0]);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
private void showCreateNumericalHabitScreen()
{
EditHabitDialog dialog;
dialog = editHabitDialogFactory.createNumerical();
activity.showDialog(dialog, "editHabit");
}
}

@ -27,9 +27,6 @@ import android.support.annotation.*;
import org.isoron.androidbase.activities.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.habits.show.*;
import java.io.*;
/**
* Activity that allows the user to see more information about a single habit.
@ -37,25 +34,16 @@ import java.io.*;
* Shows all the metadata for the habit, in addition to several charts.
*/
public class ShowHabitActivity extends BaseActivity
implements ShowHabitMenuBehavior.System
{
@Nullable
private HabitList habitList;
@Nullable
private AppComponent appComponent;
private HabitsComponent appComponent;
@Nullable
private ShowHabitScreen screen;
@Override
public File getCSVOutputDir()
{
if(appComponent == null) throw new IllegalStateException();
return appComponent.getBaseSystem().getFilesDir("CSV");
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
@ -68,7 +56,7 @@ public class ShowHabitActivity extends BaseActivity
ShowHabitComponent component = DaggerShowHabitComponent
.builder()
.appComponent(app.getComponent())
.habitsComponent(app.getComponent())
.showHabitModule(new ShowHabitModule(this, habit))
.build();

@ -28,7 +28,7 @@ import dagger.*;
@ActivityScope
@Component(modules = { ShowHabitModule.class },
dependencies = { AppComponent.class })
dependencies = { HabitsComponent.class })
public interface ShowHabitComponent
{
@NonNull

@ -21,6 +21,7 @@ package org.isoron.uhabits.activities.habits.show;
import android.support.annotation.*;
import org.isoron.androidbase.*;
import org.isoron.androidbase.activities.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.habits.show.*;
@ -57,8 +58,8 @@ public class ShowHabitModule extends ActivityModule
}
@Provides
public ShowHabitMenuBehavior.System getSystem(BaseActivity activity)
public ShowHabitMenuBehavior.System getSystem(BaseSystem system)
{
return (ShowHabitActivity) activity;
return system;
}
}

@ -54,7 +54,7 @@ public class FireSettingReceiver extends BroadcastReceiver
ReceiverComponent component =
DaggerFireSettingReceiver_ReceiverComponent
.builder()
.appComponent(app.getComponent())
.habitsComponent(app.getComponent())
.build();
allHabits = app.getComponent().getHabitList();
@ -99,7 +99,7 @@ public class FireSettingReceiver extends BroadcastReceiver
}
@ReceiverScope
@Component(dependencies = AppComponent.class)
@Component(dependencies = HabitsComponent.class)
interface ReceiverComponent
{
WidgetController getWidgetController();

@ -31,6 +31,8 @@ import java.util.*;
import javax.inject.*;
import dagger.*;
@AppScope
public class AndroidPreferences
implements SharedPreferences.OnSharedPreferenceChangeListener, Preferences

@ -36,7 +36,7 @@ public class ConnectivityReceiver extends BroadcastReceiver
if (context == null) return;
if (intent == null) return;
AppComponent component =
HabitsComponent component =
((HabitsApplication) context.getApplicationContext()).getComponent();
NetworkInfo networkInfo =

@ -73,7 +73,7 @@ public class PebbleReceiver extends PebbleDataReceiver
HabitsApplication app =
(HabitsApplication) context.getApplicationContext();
AppComponent component = app.getComponent();
HabitsComponent component = app.getComponent();
commandRunner = component.getCommandRunner();
taskRunner = component.getTaskRunner();
allHabits = component.getHabitList();

@ -56,7 +56,7 @@ public class ReminderReceiver extends BroadcastReceiver
ReminderComponent component = DaggerReminderReceiver_ReminderComponent
.builder()
.appComponent(app.getComponent())
.habitsComponent(app.getComponent())
.build();
HabitList habits = app.getComponent().getHabitList();
@ -105,7 +105,7 @@ public class ReminderReceiver extends BroadcastReceiver
}
@ReceiverScope
@Component(dependencies = AppComponent.class)
@Component(dependencies = HabitsComponent.class)
interface ReminderComponent
{
ReminderController getReminderController();

@ -56,7 +56,7 @@ public class WidgetReceiver extends BroadcastReceiver
WidgetComponent component = DaggerWidgetReceiver_WidgetComponent
.builder()
.appComponent(app.getComponent())
.habitsComponent(app.getComponent())
.build();
IntentParser parser = app.getComponent().getIntentParser();
@ -93,7 +93,7 @@ public class WidgetReceiver extends BroadcastReceiver
}
@ReceiverScope
@Component(dependencies = AppComponent.class)
@Component(dependencies = HabitsComponent.class)
interface WidgetComponent
{
WidgetController getWidgetController();

@ -69,7 +69,7 @@ public class HabitPickerDialog extends Activity
setContentView(R.layout.widget_configure_activity);
HabitsApplication app = (HabitsApplication) getApplicationContext();
AppComponent component = app.getComponent();
HabitsComponent component = app.getComponent();
habitList = component.getHabitList();
preferences = component.getWidgetPreferences();

@ -28,7 +28,6 @@ import android.view.*;
import android.widget.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.*;
import org.isoron.uhabits.activities.common.dialogs.*;
import org.junit.*;
import org.junit.runner.*;

@ -22,5 +22,13 @@ package org.isoron.uhabits.preferences;
public interface Preferences
{
void incrementLaunchCount();
boolean isFirstRun();
void setDeveloper(boolean isDeveloper);
void setFirstRun(boolean b);
void updateLastHint(int i, long startOfToday);
}

@ -0,0 +1,220 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.ui.habits.list;
import android.support.annotation.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.preferences.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.utils.*;
import java.io.*;
import java.util.*;
import javax.inject.*;
public class ListHabitsBehavior
{
private HabitList habitList;
private System system;
private TaskRunner taskRunner;
private Screen screen;
private CommandRunner commandRunner;
private Preferences prefs;
@Inject
public ListHabitsBehavior(@NonNull HabitList habitList,
@NonNull System system,
@NonNull TaskRunner taskRunner,
@NonNull Screen screen,
@NonNull CommandRunner commandRunner,
@NonNull Preferences prefs)
{
this.habitList = habitList;
this.system = system;
this.taskRunner = taskRunner;
this.screen = screen;
this.commandRunner = commandRunner;
this.prefs = prefs;
}
public void onClickHabit(@NonNull Habit h)
{
screen.showHabitScreen(h);
}
// public void onExportDB()
// {
// taskRunner.execute(exportDBFactory.create(filename -> {
// if (filename != null) screen.showSendFileScreen(filename);
// else screen.showMessage(R.string.could_not_export);
// }));
// }
public void onEdit(@NonNull Habit habit, long timestamp)
{
CheckmarkList checkmarks = habit.getCheckmarks();
double oldValue = checkmarks.getValues(timestamp, timestamp)[0];
screen.showNumberPicker(oldValue / 1000, habit.getUnit(), newValue ->
{
newValue = Math.round(newValue * 1000);
commandRunner.execute(
new CreateRepetitionCommand(habit, timestamp, (int) newValue),
habit.getId());
});
}
public void onExportCSV()
{
List<Habit> selected = new LinkedList<>();
for (Habit h : habitList) selected.add(h);
File outputDir = system.getCSVOutputDir();
taskRunner.execute(
new ExportCSVTask(habitList, selected, outputDir, filename ->
{
if (filename != null) screen.showSendFileScreen(filename);
else screen.showMessage(Message.COULD_NOT_EXPORT);
}));
}
// public void onImportData(@NonNull File file,
// @NonNull OnFinishedListener finishedListener)
// {
// taskRunner.execute(importTaskFactory.create(file, result -> {
// switch (result)
// {
// case ImportDataTask.SUCCESS:
// adapter.refresh();
// screen.showMessage(R.string.habits_imported);
// break;
//
// case ImportDataTask.NOT_RECOGNIZED:
// screen.showMessage(R.string.file_not_recognized);
// break;
//
// default:
// screen.showMessage(R.string.could_not_import);
// break;
// }
//
// finishedListener.onFinish();
// }));
// }
public void onReorderHabit(@NonNull Habit from, @NonNull Habit to)
{
taskRunner.execute(() -> habitList.reorder(from, to));
}
public void onRepairDB()
{
taskRunner.execute(() ->
{
habitList.repair();
screen.showMessage(Message.DATABASE_REPAIRED);
});
}
public void onSendBugReport()
{
system.dumpBugReportToFile();
try
{
String log = system.getBugReport();
screen.showSendBugReportToDeveloperScreen(log);
}
catch (IOException e)
{
e.printStackTrace();
screen.showMessage(Message.COULD_NOT_GENERATE_BUG_REPORT);
}
}
public void onStartup()
{
prefs.incrementLaunchCount();
if (prefs.isFirstRun()) onFirstRun();
}
public void onToggle(@NonNull Habit habit, long timestamp)
{
commandRunner.execute(new ToggleRepetitionCommand(habit, timestamp),
habit.getId());
}
public void onFirstRun()
{
prefs.setFirstRun(false);
prefs.updateLastHint(-1, DateUtils.getStartOfToday());
screen.showIntroScreen();
}
public enum Message
{
COULD_NOT_EXPORT, IMPORT_SUCCESSFUL, IMPORT_FAILED, DATABASE_REPAIRED,
COULD_NOT_GENERATE_BUG_REPORT, FILE_NOT_RECOGNIZED
}
public interface NumberPickerCallback
{
void onNumberPicked(double newValue);
}
public interface OnFinishedListener
{
void onFinish();
}
public interface Screen
{
void showHabitScreen(@NonNull Habit h);
void showIntroScreen();
void showMessage(@NonNull Message m);
void showNumberPicker(double value,
@NonNull String unit,
@NonNull NumberPickerCallback callback);
void showSendBugReportToDeveloperScreen(String log);
void showSendFileScreen(@NonNull String filename);
}
public interface System
{
void dumpBugReportToFile();
String getBugReport() throws IOException;
File getCSVOutputDir();
}
}

@ -19,6 +19,7 @@
package org.isoron.uhabits;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.models.memory.*;
import org.isoron.uhabits.tasks.*;
@ -27,6 +28,8 @@ import org.junit.*;
import java.util.*;
import static org.mockito.Mockito.*;
public class BaseUnitTest
{
protected HabitList habitList;
@ -37,6 +40,8 @@ public class BaseUnitTest
protected SingleThreadTaskRunner taskRunner;
protected CommandRunner commandRunner;
@Before
public void setUp()
{
@ -45,9 +50,10 @@ public class BaseUnitTest
DateUtils.setFixedLocalTime(fixed_local_time);
modelFactory = new MemoryModelFactory();
habitList = modelFactory.buildHabitList();
habitList = spy(modelFactory.buildHabitList());
fixtures = new HabitFixtures(modelFactory);
taskRunner = new SingleThreadTaskRunner();
commandRunner = new CommandRunner(taskRunner);
}
@After

@ -62,6 +62,31 @@ public class HabitFixtures
return habit;
}
public Habit createNumericalHabit()
{
Habit habit = modelFactory.buildHabit();
habit.setType(Habit.NUMBER_HABIT);
habit.setName("Run");
habit.setDescription("How many miles did you run today?");
habit.setUnit("miles");
habit.setTargetType(Habit.AT_LEAST);
habit.setTargetValue(2.0);
habit.setColor(1);
long day = DateUtils.millisecondsInOneDay;
long today = DateUtils.getStartOfToday();
int times[] = { 0, 1, 3, 5, 7, 8, 9, 10 };
int values[] = { 100, 200, 300, 400, 500, 600, 700, 800 };
for(int i = 0; i < times.length; i++)
{
long timestamp = today - times[i] * day;
habit.getRepetitions().add(new Repetition(timestamp, values[i]));
}
return habit;
}
public Habit createShortHabit()
{
Habit habit = modelFactory.buildHabit();

@ -0,0 +1,132 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.ui.habits.list;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.preferences.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
import org.mockito.*;
import org.mockito.junit.*;
import static junit.framework.TestCase.assertTrue;
import static org.hamcrest.CoreMatchers.*;
import static org.isoron.uhabits.ui.habits.list.ListHabitsBehavior.Message.*;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class ListHabitsBehaviorTest extends BaseUnitTest
{
@Mock
private ListHabitsBehavior.System system;
@Mock
private Preferences prefs;
private ListHabitsBehavior behavior;
@Mock
private ListHabitsBehavior.Screen screen;
private Habit habit1, habit2;
@Captor
ArgumentCaptor<ListHabitsBehavior.NumberPickerCallback> captor;
@Override
@Before
public void setUp()
{
super.setUp();
habit1 = fixtures.createShortHabit();
habit2 = fixtures.createNumericalHabit();
habitList.add(habit1);
habitList.add(habit2);
clearInvocations(habitList);
behavior = new ListHabitsBehavior(habitList, system, taskRunner, screen,
commandRunner, prefs);
}
@Test
public void testOnEdit()
{
behavior.onEdit(habit2, DateUtils.getStartOfToday());
verify(screen).showNumberPicker(eq(0.1), eq("miles"), captor.capture());
captor.getValue().onNumberPicked(100);
assertThat(habit2.getCheckmarks().getTodayValue(), equalTo(100000));
}
@Test
public void testOnHabitClick()
{
behavior.onClickHabit(habit1);
verify(screen).showHabitScreen(habit1);
}
@Test
public void testOnHabitReorder()
{
Habit from = habit1;
Habit to = habit2;
behavior.onReorderHabit(from, to);
verify(habitList).reorder(from, to);
}
@Test
public void testOnRepairDB()
{
behavior.onRepairDB();
verify(habitList).repair();
verify(screen).showMessage(DATABASE_REPAIRED);
}
@Test
public void testOnStartup_firstLaunch()
{
long today = DateUtils.getStartOfToday();
when(prefs.isFirstRun()).thenReturn(true);
behavior.onStartup();
verify(prefs).setFirstRun(false);
verify(prefs).updateLastHint(-1, today);
verify(screen).showIntroScreen();
}
@Test
public void testOnStartup_notFirstLaunch()
{
when(prefs.isFirstRun()).thenReturn(false);
behavior.onStartup();
verify(prefs).incrementLaunchCount();
}
@Test
public void testOnToggle()
{
assertTrue(habit1.isCompletedToday());
behavior.onToggle(habit1, DateUtils.getStartOfToday());
assertFalse(habit1.isCompletedToday());
}
}
Loading…
Cancel
Save