Some refactoring; add tests for ListHabitsScreen

pull/151/head
Alinson S. Xavier 9 years ago
parent d6dacfd24b
commit a984467516

@ -24,15 +24,12 @@ import android.content.*;
import android.os.*;
import android.support.annotation.*;
import android.support.test.*;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
import java.util.*;
import java.util.concurrent.*;
@ -43,8 +40,6 @@ import static junit.framework.Assert.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class BaseAndroidTest
{
// 8:00am, January 25th, 2015 (UTC)

@ -21,11 +21,8 @@ package org.isoron.uhabits;
import android.content.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.models.sqlite.*;
import org.isoron.uhabits.utils.*;
import javax.inject.*;
@ -42,52 +39,21 @@ public class AndroidModule
{
@Provides
@Singleton
CommandRunner provideCommandRunner()
{
return new CommandRunner();
}
@Provides
@Singleton
HabitList provideHabitList()
static HabitList provideHabitList()
{
return SQLiteHabitList.getInstance();
}
@Provides
ModelFactory provideModelFactory()
static ModelFactory provideModelFactory()
{
return new SQLModelFactory();
}
@Provides
@Singleton
Preferences providePreferences()
{
return new Preferences();
}
@Provides
@Singleton
WidgetPreferences provideWidgetPreferences()
{
return new WidgetPreferences();
}
@Provides
@Singleton
ReminderScheduler provideReminderScheduler()
{
Context context = HabitsApplication.getContext();
IntentScheduler intentScheduler = new IntentScheduler(context);
IntentFactory intentFactory = new IntentFactory(context);
return new ReminderScheduler(intentFactory, intentScheduler);
}
@Provides
@Singleton
HabitLogger provideLogger()
static Context provideApplicationContext()
{
return new HabitLogger();
return HabitsApplication.getContext();
}
}

@ -20,11 +20,13 @@
package org.isoron.uhabits;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.io.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.receivers.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.ui.*;
import org.isoron.uhabits.ui.common.dialogs.*;
import org.isoron.uhabits.ui.habits.edit.*;
import org.isoron.uhabits.ui.habits.list.*;
import org.isoron.uhabits.ui.habits.list.controllers.*;
@ -40,6 +42,8 @@ import org.isoron.uhabits.widgets.*;
*/
public interface BaseComponent
{
IntentFactory getIntentFactory();
void inject(CheckmarkButtonController checkmarkButtonController);
void inject(ListHabitsController listHabitsController);
@ -90,7 +94,7 @@ public interface BaseComponent
void inject(HabitsCSVExporter habitsCSVExporter);
void inject(BaseDialogFragment baseDialogFragment);
void inject(BaseDialog baseDialog);
void inject(ShowHabitController showHabitController);
@ -109,4 +113,12 @@ public interface BaseComponent
void inject(ReminderReceiver reminderReceiver);
void inject(ReminderScheduler reminderScheduler);
void inject(ListHabitsScreen listHabitsScreen);
void inject(ShowHabitScreen showHabitScreen);
void inject(ConfirmDeleteDialog confirmDeleteDialog);
void inject(PendingIntentFactory pendingIntentFactory);
}

@ -28,8 +28,17 @@ import org.isoron.uhabits.utils.*;
import java.text.*;
import java.util.*;
import javax.inject.*;
@Singleton
public class HabitLogger
{
@Inject
public HabitLogger()
{
}
public void logReminderScheduled(@NonNull Habit habit,
@NonNull Long reminderTime)
{

@ -26,16 +26,20 @@ import org.isoron.uhabits.tasks.BaseTask;
import java.util.LinkedList;
import javax.inject.*;
/**
* A CommandRunner executes and undoes commands.
* <p>
* CommandRunners also allows objects to subscribe to it, and receive events
* whenever a command is performed.
*/
@Singleton
public class CommandRunner
{
private LinkedList<Listener> listeners;
@Inject
public CommandRunner()
{
listeners = new LinkedList<>();

@ -19,94 +19,53 @@
package org.isoron.uhabits.intents;
import android.app.*;
import android.content.*;
import android.net.*;
import android.support.annotation.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.receivers.*;
import org.isoron.uhabits.ui.about.*;
import org.isoron.uhabits.ui.habits.show.*;
import org.isoron.uhabits.ui.intro.*;
import org.isoron.uhabits.ui.settings.*;
import static android.app.PendingIntent.*;
import javax.inject.*;
public class IntentFactory
{
@NonNull
private final Context context;
public IntentFactory(@NonNull Context context)
@Inject
public IntentFactory()
{
this.context = context;
}
public PendingIntent buildAddCheckmark(@NonNull Habit habit,
@Nullable Long timestamp)
{
Uri data = habit.getUri();
Intent checkIntent = new Intent(context, WidgetReceiver.class);
checkIntent.setData(data);
checkIntent.setAction(WidgetReceiver.ACTION_ADD_REPETITION);
if (timestamp != null) checkIntent.putExtra("timestamp", timestamp);
return PendingIntent.getBroadcast(context, 1, checkIntent,
FLAG_UPDATE_CURRENT);
}
public PendingIntent buildDismissNotification()
public Intent startAboutActivity(Context context)
{
Intent deleteIntent = new Intent(context, ReminderReceiver.class);
deleteIntent.setAction(WidgetReceiver.ACTION_DISMISS_REMINDER);
return PendingIntent.getBroadcast(context, 0, deleteIntent,
FLAG_UPDATE_CURRENT);
return new Intent(context, AboutActivity.class);
}
public PendingIntent buildShowReminder(@NonNull Habit habit,
@Nullable Long reminderTime,
long timestamp)
public Intent startIntroActivity(Context context)
{
Uri uri = habit.getUri();
Intent intent = new Intent(context, ReminderReceiver.class);
intent.setAction(ReminderReceiver.ACTION_SHOW_REMINDER);
intent.setData(uri);
intent.putExtra("timestamp", timestamp);
intent.putExtra("reminderTime", reminderTime);
int reqCode = ((int) (habit.getId() % Integer.MAX_VALUE)) + 1;
return PendingIntent.getBroadcast(context, reqCode, intent,
FLAG_UPDATE_CURRENT);
return new Intent(context, IntroActivity.class);
}
public PendingIntent buildSnoozeNotification(@NonNull Habit habit)
public Intent startSettingsActivity(Context context)
{
Uri data = habit.getUri();
Intent snoozeIntent = new Intent(context, ReminderReceiver.class);
snoozeIntent.setData(data);
snoozeIntent.setAction(ReminderReceiver.ACTION_SNOOZE_REMINDER);
return PendingIntent.getBroadcast(context, 0, snoozeIntent,
FLAG_UPDATE_CURRENT);
return new Intent(context, SettingsActivity.class);
}
public PendingIntent buildToggleCheckmark(@NonNull Habit habit,
@Nullable Long timestamp)
public Intent startShowHabitActivity(Context context, Habit habit)
{
Uri data = habit.getUri();
Intent checkIntent = new Intent(context, WidgetReceiver.class);
checkIntent.setData(data);
checkIntent.setAction(WidgetReceiver.ACTION_TOGGLE_REPETITION);
if (timestamp != null) checkIntent.putExtra("timestamp", timestamp);
return PendingIntent.getBroadcast(context, 2, checkIntent,
FLAG_UPDATE_CURRENT);
Intent intent = new Intent(context, ShowHabitActivity.class);
intent.setData(habit.getUri());
return intent;
}
public PendingIntent buildViewHabit(Habit habit)
public Intent viewFAQ(Context context)
{
Uri uri = habit.getUri();
Intent intent = new Intent(context, ShowHabitActivity.class);
intent.setData(uri);
return android.support.v4.app.TaskStackBuilder
.create(context)
.addNextIntentWithParentStack(intent)
.getPendingIntent(0, FLAG_UPDATE_CURRENT);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(context.getString(R.string.helpURL)));
return intent;
}
}

@ -24,13 +24,17 @@ import android.content.*;
import android.os.*;
import android.support.annotation.*;
import javax.inject.*;
import static android.app.AlarmManager.*;
import static android.content.Context.*;
@Singleton
public class IntentScheduler
{
private final AlarmManager manager;
@Inject
public IntentScheduler(@NonNull Context context)
{
manager = (AlarmManager) context.getSystemService(ALARM_SERVICE);

@ -0,0 +1,118 @@
/*
* Copyright (C) 2016 Á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.intents;
import android.app.*;
import android.content.*;
import android.net.*;
import android.support.annotation.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.receivers.*;
import javax.inject.*;
import static android.app.PendingIntent.*;
@Singleton
public class PendingIntentFactory
{
@NonNull
private final Context context;
@Inject
protected IntentFactory intentFactory;
@Inject
public PendingIntentFactory(@NonNull Context context)
{
this.context = context;
HabitsApplication.getComponent().inject(this);
}
public PendingIntent addCheckmark(@NonNull Habit habit,
@Nullable Long timestamp)
{
Uri data = habit.getUri();
Intent checkIntent = new Intent(context, WidgetReceiver.class);
checkIntent.setData(data);
checkIntent.setAction(WidgetReceiver.ACTION_ADD_REPETITION);
if (timestamp != null) checkIntent.putExtra("timestamp", timestamp);
return PendingIntent.getBroadcast(context, 1, checkIntent,
FLAG_UPDATE_CURRENT);
}
public PendingIntent dismissNotification()
{
Intent deleteIntent = new Intent(context, ReminderReceiver.class);
deleteIntent.setAction(WidgetReceiver.ACTION_DISMISS_REMINDER);
return PendingIntent.getBroadcast(context, 0, deleteIntent,
FLAG_UPDATE_CURRENT);
}
public PendingIntent showReminder(@NonNull Habit habit,
@Nullable Long reminderTime,
long timestamp)
{
Uri uri = habit.getUri();
Intent intent = new Intent(context, ReminderReceiver.class);
intent.setAction(ReminderReceiver.ACTION_SHOW_REMINDER);
intent.setData(uri);
intent.putExtra("timestamp", timestamp);
intent.putExtra("reminderTime", reminderTime);
int reqCode = ((int) (habit.getId() % Integer.MAX_VALUE)) + 1;
return PendingIntent.getBroadcast(context, reqCode, intent,
FLAG_UPDATE_CURRENT);
}
public PendingIntent snoozeNotification(@NonNull Habit habit)
{
Uri data = habit.getUri();
Intent snoozeIntent = new Intent(context, ReminderReceiver.class);
snoozeIntent.setData(data);
snoozeIntent.setAction(ReminderReceiver.ACTION_SNOOZE_REMINDER);
return PendingIntent.getBroadcast(context, 0, snoozeIntent,
FLAG_UPDATE_CURRENT);
}
public PendingIntent toggleCheckmark(@NonNull Habit habit,
@Nullable Long timestamp)
{
Uri data = habit.getUri();
Intent checkIntent = new Intent(context, WidgetReceiver.class);
checkIntent.setData(data);
checkIntent.setAction(WidgetReceiver.ACTION_TOGGLE_REPETITION);
if (timestamp != null) checkIntent.putExtra("timestamp", timestamp);
return PendingIntent.getBroadcast(context, 2, checkIntent,
FLAG_UPDATE_CURRENT);
}
public PendingIntent showHabit(Habit habit)
{
Intent intent = intentFactory.startShowHabitActivity(context, habit);
return android.support.v4.app.TaskStackBuilder
.create(context)
.addNextIntentWithParentStack(intent)
.getPendingIntent(0, FLAG_UPDATE_CURRENT);
}
}

@ -0,0 +1,106 @@
/*
* Copyright (C) 2016 Á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.io;
import android.content.*;
import android.os.*;
import android.support.annotation.*;
import android.util.*;
import org.isoron.uhabits.*;
import java.io.*;
import javax.inject.*;
import static android.support.v4.content.ContextCompat.*;
/**
* A DirFinder locates suitable directories for storing user files.
*/
public class DirFinder
{
private static final String TAG = "DirFinder";
private final Context context;
@Inject
public DirFinder()
{
context = HabitsApplication.getContext();
}
@Nullable
public File findSDCardDir(@Nullable String subpath)
{
File parents[] = new File[]{
Environment.getExternalStorageDirectory()
};
return findDir(parents, subpath);
}
@Nullable
public File findStorageDir(@Nullable String relativePath)
{
File potentialParents[] = getExternalFilesDirs(context, null);
if (potentialParents == null)
{
Log.e(TAG, "getFilesDir: getExternalFilesDirs returned null");
return null;
}
return findDir(potentialParents, relativePath);
}
@Nullable
private File findDir(@NonNull File potentialParents[],
@Nullable String relativePath)
{
if (relativePath == null) relativePath = "";
File chosenDir = null;
for (File dir : potentialParents)
{
if (dir == null || !dir.canWrite()) continue;
chosenDir = dir;
break;
}
if (chosenDir == null)
{
Log.e(TAG,
"getDir: all potential parents are null or non-writable");
return null;
}
File dir = new File(
String.format("%s/%s/", chosenDir.getAbsolutePath(), relativePath));
if (!dir.exists() && !dir.mkdirs())
{
Log.e(TAG,
"getDir: chosen dir does not exist and cannot be created");
return null;
}
return dir;
}
}

@ -21,11 +21,20 @@ package org.isoron.uhabits.models.sqlite;
import org.isoron.uhabits.models.*;
import javax.inject.*;
/**
* Factory that provides models backed by an SQLite database.
*/
@Singleton
public class SQLModelFactory implements ModelFactory
{
@Inject
public SQLModelFactory()
{
}
@Override
public RepetitionList buildRepetitionList(Habit habit)
{

@ -145,14 +145,15 @@ public class ReminderReceiver extends BroadcastReceiver
context, 0, contentIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
IntentFactory intentFactory = new IntentFactory(context);
PendingIntentFactory
pendingIntentFactory = new PendingIntentFactory(context);
PendingIntent dismissPendingIntent;
dismissPendingIntent = intentFactory.buildDismissNotification();
dismissPendingIntent = pendingIntentFactory.dismissNotification();
PendingIntent checkIntentPending =
intentFactory.buildAddCheckmark(habit, timestamp);
pendingIntentFactory.addCheckmark(habit, timestamp);
PendingIntent snoozeIntentPending =
intentFactory.buildSnoozeNotification(habit);
pendingIntentFactory.snoozeNotification(habit);
Uri ringtoneUri = RingtoneUtils.getRingtoneUri(context);

@ -24,6 +24,7 @@ import android.os.*;
import android.support.annotation.*;
import android.support.v7.app.*;
import android.view.*;
import android.widget.*;
import org.isoron.uhabits.utils.*;
@ -51,6 +52,8 @@ abstract public class BaseActivity extends AppCompatActivity
@Nullable
private BaseScreen screen;
private Toast toast;
@Override
public boolean onCreateOptionsMenu(@Nullable Menu menu)
{
@ -78,6 +81,30 @@ abstract public class BaseActivity extends AppCompatActivity
this.screen = screen;
}
public void showDialog(AppCompatDialogFragment dialog, String tag)
{
dialog.show(getSupportFragmentManager(), tag);
}
public void showDialog(AppCompatDialog dialog)
{
dialog.show();
}
/**
* Shows a message on the screen.
*
* @param stringId the string resource id for this message.
*/
public void showMessage(@StringRes Integer stringId)
{
if (stringId == null) return;
if (toast == null)
toast = Toast.makeText(this, stringId, Toast.LENGTH_SHORT);
else toast.setText(stringId);
toast.show();
}
@Override
public void uncaughtException(@Nullable Thread thread,
@Nullable Throwable ex)

@ -27,12 +27,11 @@ import android.os.*;
import android.support.annotation.*;
import android.support.v7.app.*;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.Toolbar;
import android.support.v7.widget.*;
import android.view.*;
import android.widget.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.tasks.ProgressBar;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.utils.*;
import java.io.*;
@ -48,8 +47,6 @@ public abstract class BaseScreen
{
protected BaseActivity activity;
private Toast toast;
@Nullable
private BaseRootView rootView;
@ -155,6 +152,11 @@ public abstract class BaseScreen
activity.setBaseMenu(menu);
}
public void showMessage(@StringRes int stringId)
{
activity.showMessage(stringId);
}
/**
* Sets the root view for this screen.
*
@ -179,20 +181,6 @@ public abstract class BaseScreen
this.selectionMenu = menu;
}
/**
* Shows a message on the screen.
*
* @param stringId the string resource id for this message.
*/
public void showMessage(@Nullable Integer stringId)
{
if (stringId == null) return;
if (toast == null)
toast = Toast.makeText(activity, stringId, Toast.LENGTH_SHORT);
else toast.setText(stringId);
toast.show();
}
public void showSendEmailScreen(String to, String subject, String content)
{
Intent intent = new Intent();
@ -225,11 +213,6 @@ public abstract class BaseScreen
activity.startSupportActionMode(new ActionModeWrapper());
}
protected void showDialog(AppCompatDialogFragment dialog, String tag)
{
dialog.show(activity.getSupportFragmentManager(), tag);
}
public void invalidateToolbar()
{
if (rootView == null) return;

@ -0,0 +1,58 @@
/*
* Copyright (C) 2016 Á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.common.dialogs;
import android.content.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.utils.*;
/**
* Dialog that allows the user to choose a color.
*/
public class ColorPickerDialog extends com.android.colorpicker.ColorPickerDialog
{
public static ColorPickerDialog newInstance(int paletteColor)
{
ColorPickerDialog dialog = new ColorPickerDialog();
Context context = dialog.getContext();
int color = ColorUtils.getColor(context, paletteColor);
dialog.initialize(R.string.color_picker_default_title,
ColorUtils.getPalette(context), color, 4,
com.android.colorpicker.ColorPickerDialog.SIZE_SMALL);
return dialog;
}
public void setListener(OnColorSelectedListener listener)
{
super.setOnColorSelectedListener(c -> {
c = ColorUtils.colorToPaletteIndex(getContext(), c);
listener.onColorSelected(c);
});
}
public interface OnColorSelectedListener
{
void onColorSelected(int color);
}
}

@ -0,0 +1,60 @@
/*
* Copyright (C) 2016 Á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.common.dialogs;
import android.content.*;
import android.support.annotation.*;
import android.support.v7.app.*;
import org.isoron.uhabits.*;
import butterknife.*;
/**
* Dialog that asks the user confirmation before executing a delete operation.
*/
public class ConfirmDeleteDialog extends AlertDialog
{
@BindString(R.string.delete_habits_message)
protected String question;
@BindString(android.R.string.yes)
protected String yes;
@BindString(android.R.string.no)
protected String no;
protected ConfirmDeleteDialog(@NonNull Context context,
@NonNull Callback callback)
{
super(context);
ButterKnife.bind(this);
setTitle(R.string.delete_habits);
setMessage(question);
setButton(BUTTON_POSITIVE, yes, (dialog, which) -> callback.run());
setButton(BUTTON_NEGATIVE, no, (dialog, which) -> {});
}
public interface Callback
{
void run();
}
}

@ -0,0 +1,71 @@
/*
* Copyright (C) 2016 Á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.common.dialogs;
import android.content.*;
import android.support.annotation.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.habits.edit.*;
import java.io.*;
import javax.inject.*;
public class DialogFactory
{
@Inject
public DialogFactory()
{
}
@NonNull
public ColorPickerDialog buildColorPicker(int paletteColor)
{
return ColorPickerDialog.newInstance(paletteColor);
}
@NonNull
public ConfirmDeleteDialog buildConfirmDeleteDialog(
@NonNull Context context,
@NonNull ConfirmDeleteDialog.Callback callback)
{
return new ConfirmDeleteDialog(context, callback);
}
@NonNull
public CreateHabitDialog buildCreateHabitDialog()
{
return new CreateHabitDialog();
}
@NonNull
public EditHabitDialog buildEditHabitDialog(Habit habit)
{
return EditHabitDialog.newInstance(habit);
}
@NonNull
public FilePickerDialog buildFilePicker(Context context, File dir)
{
return new FilePickerDialog(context, dir);
}
}

@ -17,50 +17,43 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.ui.settings;
import android.app.Activity;
import android.app.Dialog;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager.LayoutParams;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
package org.isoron.uhabits.ui.common.dialogs;
import android.content.*;
import android.support.annotation.*;
import android.support.v7.app.*;
import android.view.*;
import android.view.WindowManager.*;
import android.widget.*;
import java.io.*;
import java.util.*;
/**
* Dialog that allows the user to pick a file.
*/
public class FilePickerDialog implements AdapterView.OnItemClickListener
{
private static final String PARENT_DIR = "..";
private final Activity activity;
private final Context context;
private ListView list;
private Dialog dialog;
private AppCompatDialog dialog;
private File currentPath;
public interface OnFileSelectedListener
{
void onFileSelected(File file);
}
private OnFileSelectedListener listener;
public FilePickerDialog(Activity activity, File initialDirectory)
public FilePickerDialog(Context context, File initialDirectory)
{
this.activity = activity;
this.context = context;
list = new ListView(activity);
list = new ListView(context);
list.setOnItemClickListener(this);
dialog = new Dialog(activity);
dialog = new AppCompatDialog(context);
dialog.setContentView(list);
dialog
.getWindow()
@ -69,6 +62,11 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener
navigateTo(initialDirectory);
}
public AppCompatDialog getDialog()
{
return dialog;
}
@Override
public void onItemClick(AdapterView<?> parent,
View view,
@ -92,27 +90,14 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener
}
}
public void show()
{
dialog.show();
}
public void setListener(OnFileSelectedListener listener)
{
this.listener = listener;
}
private void navigateTo(File path)
public void show()
{
if (!path.exists()) return;
File[] dirs = path.listFiles(new ReadableDirFilter());
File[] files = path.listFiles(new RegularReadableFileFilter());
if (dirs == null || files == null) return;
this.currentPath = path;
dialog.setTitle(currentPath.getPath());
list.setAdapter(new FilePickerAdapter(getFileList(path, dirs, files)));
dialog.show();
}
@NonNull
@ -144,11 +129,38 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener
return fileList;
}
private void navigateTo(File path)
{
if (!path.exists()) return;
File[] dirs = path.listFiles(new ReadableDirFilter());
File[] files = path.listFiles(new RegularReadableFileFilter());
if (dirs == null || files == null) return;
this.currentPath = path;
dialog.setTitle(currentPath.getPath());
list.setAdapter(new FilePickerAdapter(getFileList(path, dirs, files)));
}
public interface OnFileSelectedListener
{
void onFileSelected(File file);
}
private static class ReadableDirFilter implements FileFilter
{
@Override
public boolean accept(File file)
{
return (file.isDirectory() && file.canRead());
}
}
private class FilePickerAdapter extends ArrayAdapter<String>
{
public FilePickerAdapter(@NonNull String[] fileList)
{
super(FilePickerDialog.this.activity,
super(FilePickerDialog.this.context,
android.R.layout.simple_list_item_1, fileList);
}
@ -162,15 +174,6 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener
}
}
private static class ReadableDirFilter implements FileFilter
{
@Override
public boolean accept(File file)
{
return (file.isDirectory() && file.canRead());
}
}
private class RegularReadableFileFilter implements FileFilter
{
@Override

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.ui.habits.edit;
package org.isoron.uhabits.ui.common.dialogs;
import android.app.*;
import android.content.*;

@ -17,39 +17,39 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.ui.habits.edit;
package org.isoron.uhabits.ui.common.dialogs;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.app.*;
import android.content.*;
import android.os.*;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDialogFragment;
import android.support.v7.app.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.utils.DateUtils;
import org.isoron.uhabits.*;
import org.isoron.uhabits.utils.*;
/**
* Dialog that allows the user to pick one or more days of the week.
*/
public class WeekdayPickerDialog extends AppCompatDialogFragment implements
DialogInterface.OnMultiChoiceClickListener,
DialogInterface.OnClickListener
{
public interface OnWeekdaysPickedListener
{
void onWeekdaysPicked(boolean[] selectedDays);
}
private boolean[] selectedDays;
private OnWeekdaysPickedListener listener;
public void setListener(OnWeekdaysPickedListener listener)
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked)
{
this.listener = listener;
selectedDays[which] = isChecked;
}
public void setSelectedDays(boolean[] selectedDays)
@Override
public void onClick(DialogInterface dialog, int which)
{
this.selectedDays = selectedDays;
if (listener != null) listener.onWeekdaysPicked(selectedDays);
}
@Override
@ -61,28 +61,25 @@ public class WeekdayPickerDialog extends AppCompatDialogFragment implements
.setMultiChoiceItems(DateUtils.getLongDayNames(), selectedDays,
this)
.setPositiveButton(android.R.string.yes, this)
.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dismiss();
}
});
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
dismiss();
});
return builder.create();
}
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked)
public void setListener(OnWeekdaysPickedListener listener)
{
selectedDays[which] = isChecked;
this.listener = listener;
}
@Override
public void onClick(DialogInterface dialog, int which)
public void setSelectedDays(boolean[] selectedDays)
{
if (listener != null) listener.onWeekdaysPicked(selectedDays);
this.selectedDays = selectedDays;
}
public interface OnWeekdaysPickedListener
{
void onWeekdaysPicked(boolean[] selectedDays);
}
}

@ -25,14 +25,14 @@ import android.support.v7.app.*;
import android.text.format.*;
import android.view.*;
import com.android.colorpicker.*;
import com.android.datetimepicker.time.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.ui.common.dialogs.*;
import org.isoron.uhabits.utils.DateUtils;
import org.isoron.uhabits.utils.*;
import java.util.*;
@ -40,7 +40,7 @@ import javax.inject.*;
import butterknife.*;
public abstract class BaseDialogFragment extends AppCompatDialogFragment
public abstract class BaseDialog extends AppCompatDialogFragment
{
@Nullable
protected Habit originalHabit;
@ -60,6 +60,9 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment
@Inject
protected HabitList habitList;
@Inject
protected DialogFactory dialogFactory;
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
@ -83,7 +86,8 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment
if (position < 0 || position > 4) throw new IllegalArgumentException();
int freqNums[] = { 1, 1, 2, 5, 3 };
int freqDens[] = { 1, 7, 7, 7, 7 };
modifiedHabit.setFrequency(new Frequency(freqNums[position], freqDens[position]));
modifiedHabit.setFrequency(
new Frequency(freqNums[position], freqDens[position]));
helper.populateFrequencyFields(modifiedHabit);
}
@ -169,23 +173,22 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment
WeekdayPickerDialog dialog = new WeekdayPickerDialog();
dialog.setListener(new OnWeekdaysPickedListener());
dialog.setSelectedDays(
DateUtils.unpackWeekdayList(reminder.getDays()));
dialog.setSelectedDays(DateUtils.unpackWeekdayList(reminder.getDays()));
dialog.show(getFragmentManager(), "weekdayPicker");
}
@OnClick(R.id.buttonPickColor)
void showColorPicker()
{
int androidColor =
ColorUtils.getColor(getContext(), modifiedHabit.getColor());
int color = modifiedHabit.getColor();
ColorPickerDialog picker = dialogFactory.buildColorPicker(color);
ColorPickerDialog picker =
ColorPickerDialog.newInstance(R.string.color_picker_default_title,
ColorUtils.getPalette(getContext()), androidColor, 4,
ColorPickerDialog.SIZE_SMALL);
picker.setListener(c -> {
prefs.setDefaultHabitColor(c);
modifiedHabit.setColor(c);
helper.populateColor(c);
});
picker.setOnColorSelectedListener(new OnColorSelectedListener());
picker.show(getFragmentManager(), "picker");
}
@ -198,20 +201,6 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment
timePicker.show(getFragmentManager(), "timePicker");
}
private class OnColorSelectedListener
implements ColorPickerSwatch.OnColorSelectedListener
{
@Override
public void onColorSelected(int androidColor)
{
int paletteColor =
ColorUtils.colorToPaletteIndex(getActivity(), androidColor);
prefs.setDefaultHabitColor(paletteColor);
modifiedHabit.setColor(paletteColor);
helper.populateColor(paletteColor);
}
}
private class OnTimeSetListener
implements TimePickerDialog.OnTimeSetListener
{

@ -23,7 +23,7 @@ import org.isoron.uhabits.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*;
public class CreateHabitDialogFragment extends BaseDialogFragment
public class CreateHabitDialog extends BaseDialog
{
@Override
protected int getTitle()

@ -25,13 +25,16 @@ import org.isoron.uhabits.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*;
public class EditHabitDialogFragment extends BaseDialogFragment
public class EditHabitDialog extends BaseDialog
{
public static EditHabitDialogFragment newInstance(long habitId)
public static EditHabitDialog newInstance(Habit habit)
{
EditHabitDialogFragment frag = new EditHabitDialogFragment();
if(habit.getId() == null)
throw new IllegalArgumentException("habit not saved");
EditHabitDialog frag = new EditHabitDialog();
Bundle args = new Bundle();
args.putLong("habitId", habitId);
args.putLong("habitId", habit.getId());
frag.setArguments(args);
return frag;
}

@ -48,7 +48,8 @@ public class ListHabitsController
@NonNull
private final HabitList habitList;
private HabitCardListAdapter adapter;
@NonNull
private final HabitCardListAdapter adapter;
@Inject
Preferences prefs;
@ -81,6 +82,7 @@ public class ListHabitsController
if (filename != null) screen.showSendFileScreen(filename);
else screen.showMessage(R.string.could_not_export);
});
task.execute();
}

@ -20,35 +20,43 @@
package org.isoron.uhabits.ui.habits.list;
import android.content.*;
import android.net.*;
import android.os.*;
import android.support.annotation.*;
import android.support.v7.app.*;
import com.android.colorpicker.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.io.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.*;
import org.isoron.uhabits.ui.about.*;
import org.isoron.uhabits.ui.common.dialogs.*;
import org.isoron.uhabits.ui.common.dialogs.ColorPickerDialog.*;
import org.isoron.uhabits.ui.habits.edit.*;
import org.isoron.uhabits.ui.habits.show.*;
import org.isoron.uhabits.ui.intro.*;
import org.isoron.uhabits.ui.settings.*;
import org.isoron.uhabits.utils.*;
import java.io.*;
import javax.inject.*;
public class ListHabitsScreen extends BaseScreen
{
@Nullable
ListHabitsController controller;
@Inject
protected DialogFactory dialogFactory;
@Inject
protected IntentFactory intentFactory;
@Inject
protected DirFinder dirFinder;
public ListHabitsScreen(@NonNull BaseActivity activity,
ListHabitsRootView rootView)
@NonNull ListHabitsRootView rootView)
{
super(activity);
setRootView(rootView);
HabitsApplication.getComponent().inject(this);
}
@Override
@ -83,90 +91,83 @@ public class ListHabitsScreen extends BaseScreen
public void showAboutScreen()
{
Intent intent = new Intent(activity, AboutActivity.class);
Intent intent = intentFactory.startAboutActivity(activity);
activity.startActivity(intent);
}
public void showColorPicker(Habit habit, OnColorSelectedListener callback)
/**
* Displays a {@link ColorPickerDialog} to the user.
* <p>
* The selected color on the dialog is the color of the given habit.
*
* @param habit the habit
* @param callback
*/
public void showColorPicker(@NonNull Habit habit,
@NonNull OnColorSelectedListener callback)
{
int color = ColorUtils.getColor(activity, habit.getColor());
ColorPickerDialog picker =
ColorPickerDialog.newInstance(R.string.color_picker_default_title,
ColorUtils.getPalette(activity), color, 4,
ColorPickerDialog.SIZE_SMALL);
picker.setOnColorSelectedListener(c -> {
c = ColorUtils.colorToPaletteIndex(activity, c);
callback.onColorSelected(c);
});
picker.show(activity.getSupportFragmentManager(), "picker");
dialogFactory.buildColorPicker(habit.getColor());
picker.setListener(callback);
activity.showDialog(picker, "picker");
}
public void showCreateHabitScreen()
{
showDialog(new CreateHabitDialogFragment(), "editHabit");
CreateHabitDialog dialog = dialogFactory.buildCreateHabitDialog();
activity.showDialog(dialog, "editHabit");
}
public void showDeleteConfirmationScreen(Callback callback)
public void showDeleteConfirmationScreen(ConfirmDeleteDialog.Callback callback)
{
new AlertDialog.Builder(activity)
.setTitle(R.string.delete_habits)
.setMessage(R.string.delete_habits_message)
.setPositiveButton(android.R.string.yes,
(dialog, which) -> callback.run())
.setNegativeButton(android.R.string.no, null)
.show();
ConfirmDeleteDialog dialog =
dialogFactory.buildConfirmDeleteDialog(activity, callback);
activity.showDialog(dialog);
}
public void showEditHabitScreen(Habit habit)
{
BaseDialogFragment frag =
EditHabitDialogFragment.newInstance(habit.getId());
frag.show(activity.getSupportFragmentManager(), "editHabit");
EditHabitDialog dialog = dialogFactory.buildEditHabitDialog(habit);
activity.showDialog(dialog, "editHabit");
}
public void showFAQScreen()
{
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(activity.getString(R.string.helpURL)));
Intent intent = intentFactory.viewFAQ(activity);
activity.startActivity(intent);
}
public void showHabitScreen(@NonNull Habit habit)
{
Intent intent = new Intent(activity, ShowHabitActivity.class);
intent.setData(
Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId()));
Intent intent = intentFactory.startShowHabitActivity(activity, habit);
activity.startActivity(intent);
}
public void showImportScreen()
{
if (controller == null) return;
File dir = dirFinder.findStorageDir(null);
File dir = FileUtils.getFilesDir(null);
if (dir == null)
{
showMessage(R.string.could_not_import);
activity.showMessage(R.string.could_not_import);
return;
}
FilePickerDialog picker = new FilePickerDialog(activity, dir);
picker.setListener(file -> controller.onImportData(file));
picker.show();
FilePickerDialog picker = dialogFactory.buildFilePicker(activity, dir);
if (controller != null)
picker.setListener(file -> controller.onImportData(file));
activity.showDialog(picker.getDialog());
}
public void showIntroScreen()
{
Intent intent = new Intent(activity, IntroActivity.class);
Intent intent = intentFactory.startIntroActivity(activity);
activity.startActivity(intent);
}
public void showSettingsScreen()
{
Intent intent = new Intent(activity, SettingsActivity.class);
Intent intent = intentFactory.startSettingsActivity(activity);
activity.startActivityForResult(intent, 0);
}
@ -182,7 +183,7 @@ public class ListHabitsScreen extends BaseScreen
private void refreshTheme()
{
new Handler().postDelayed(() -> {
Intent intent = new Intent(activity, MainActivity.class);
Intent intent = new Intent(activity, ListHabitsScreen.class);
activity.finish();
activity.overridePendingTransition(android.R.anim.fade_in,
@ -191,14 +192,4 @@ public class ListHabitsScreen extends BaseScreen
}, 500); // HACK: Let the menu disappear first
}
interface Callback
{
void run();
}
public interface OnColorSelectedListener
{
void onColorSelected(int color);
}
}

@ -25,7 +25,7 @@ import org.isoron.uhabits.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.ui.habits.edit.*;
import org.isoron.uhabits.ui.common.dialogs.*;
import javax.inject.*;

@ -20,33 +20,38 @@
package org.isoron.uhabits.ui.habits.show;
import android.support.annotation.*;
import android.support.v4.app.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.*;
import org.isoron.uhabits.ui.common.dialogs.*;
import org.isoron.uhabits.ui.habits.edit.*;
import javax.inject.*;
public class ShowHabitScreen extends BaseScreen
{
@NonNull
private final Habit habit;
@Inject
protected DialogFactory dialogFactory;
public ShowHabitScreen(@NonNull BaseActivity activity,
@NonNull Habit habit,
ShowHabitRootView view)
{
super(activity);
HabitsApplication.getComponent().inject(this);
this.habit = habit;
setRootView(view);
}
public void showEditHabitDialog()
{
Long id = habit.getId();
if (id == null) throw new RuntimeException("habit not saved");
FragmentManager manager = activity.getSupportFragmentManager();
EditHabitDialogFragment.newInstance(id).show(manager, "editHabit");
EditHabitDialog dialog = dialogFactory.buildEditHabitDialog(habit);
activity.showDialog(dialog, "editHabit");
}
public void showEditHistoryDialog(

@ -19,11 +19,11 @@
package org.isoron.uhabits.ui.intro;
import android.graphics.Color;
import android.os.Bundle;
import android.content.*;
import android.graphics.*;
import android.os.*;
import com.github.paolorotolo.appintro.AppIntro2;
import com.github.paolorotolo.appintro.AppIntroFragment;
import com.github.paolorotolo.appintro.*;
import org.isoron.uhabits.R;

@ -20,9 +20,6 @@
package org.isoron.uhabits.ui.settings;
import android.os.*;
import android.support.annotation.*;
import android.support.v4.app.*;
import android.view.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.ui.*;

@ -45,8 +45,8 @@ public class CheckmarkWidget extends BaseWidget
@Override
public PendingIntent getOnClickPendingIntent(Context context)
{
IntentFactory factory = new IntentFactory(context);
return factory.buildToggleCheckmark(habit, null);
PendingIntentFactory factory = new PendingIntentFactory(context);
return factory.toggleCheckmark(habit, null);
}
@Override

@ -46,8 +46,8 @@ public class FrequencyWidget extends BaseWidget
@Override
public PendingIntent getOnClickPendingIntent(Context context)
{
IntentFactory factory = new IntentFactory(context);
return factory.buildViewHabit(habit);
PendingIntentFactory factory = new PendingIntentFactory(context);
return factory.showHabit(habit);
}
@Override

@ -44,8 +44,8 @@ public class HistoryWidget extends BaseWidget
@Override
public PendingIntent getOnClickPendingIntent(Context context)
{
IntentFactory factory = new IntentFactory(context);
return factory.buildViewHabit(habit);
PendingIntentFactory factory = new PendingIntentFactory(context);
return factory.showHabit(habit);
}
@Override

@ -47,8 +47,8 @@ public class ScoreWidget extends BaseWidget
@Override
public PendingIntent getOnClickPendingIntent(Context context)
{
IntentFactory factory = new IntentFactory(context);
return factory.buildViewHabit(habit);
PendingIntentFactory factory = new PendingIntentFactory(context);
return factory.showHabit(habit);
}
@Override

@ -49,8 +49,8 @@ public class StreakWidget extends BaseWidget
@Override
public PendingIntent getOnClickPendingIntent(Context context)
{
IntentFactory factory = new IntentFactory(context);
return factory.buildViewHabit(habit);
PendingIntentFactory factory = new PendingIntentFactory(context);
return factory.showHabit(habit);
}
@Override

@ -19,21 +19,15 @@
package org.isoron.uhabits.utils;
import android.content.Context;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import org.isoron.uhabits.HabitsApplication;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.content.*;
import android.os.*;
import android.support.annotation.*;
import android.support.v4.content.*;
import android.util.*;
import org.isoron.uhabits.*;
import java.io.*;
public abstract class FileUtils
{
@ -60,60 +54,60 @@ public abstract class FileUtils
}
@Nullable
public static File getSDCardDir(@Nullable String relativePath)
private static File getDir(@NonNull File potentialParentDirs[],
@Nullable String relativePath)
{
File parents[] = new File[]{ Environment.getExternalStorageDirectory() };
return getDir(parents, relativePath);
}
@Nullable
public static File getFilesDir(@Nullable String relativePath)
{
Context context = HabitsApplication.getContext();
if(context == null)
{
Log.e("DatabaseHelper", "getFilesDir: no application context available");
return null;
}
File externalFilesDirs[] = ContextCompat.getExternalFilesDirs(context, null);
if(externalFilesDirs == null)
{
Log.e("DatabaseHelper", "getFilesDir: getExternalFilesDirs returned null");
return null;
}
return getDir(externalFilesDirs, relativePath);
}
@Nullable
private static File getDir(@NonNull File potentialParentDirs[], @Nullable String relativePath)
{
if(relativePath == null) relativePath = "";
if (relativePath == null) relativePath = "";
File chosenDir = null;
for(File dir : potentialParentDirs)
for (File dir : potentialParentDirs)
{
if (dir == null || !dir.canWrite()) continue;
chosenDir = dir;
break;
}
if(chosenDir == null)
if (chosenDir == null)
{
Log.e("DatabaseHelper", "getDir: all potential parents are null or non-writable");
Log.e("DatabaseHelper",
"getDir: all potential parents are null or non-writable");
return null;
}
File dir = new File(String.format("%s/%s/", chosenDir.getAbsolutePath(), relativePath));
File dir = new File(
String.format("%s/%s/", chosenDir.getAbsolutePath(), relativePath));
if (!dir.exists() && !dir.mkdirs())
{
Log.e("DatabaseHelper", "getDir: chosen dir does not exist and cannot be created");
Log.e("DatabaseHelper",
"getDir: chosen dir does not exist and cannot be created");
return null;
}
return dir;
}
@Nullable
public static File getFilesDir(@Nullable String relativePath)
{
Context context = HabitsApplication.getContext();
File externalFilesDirs[] =
ContextCompat.getExternalFilesDirs(context, null);
if (externalFilesDirs == null)
{
Log.e("DatabaseHelper",
"getFilesDir: getExternalFilesDirs returned null");
return null;
}
return getDir(externalFilesDirs, relativePath);
}
@Nullable
public static File getSDCardDir(@Nullable String relativePath)
{
File parents[] =
new File[]{ Environment.getExternalStorageDirectory() };
return getDir(parents, relativePath);
}
}

@ -24,12 +24,16 @@ import android.preference.*;
import org.isoron.uhabits.*;
import javax.inject.*;
@Singleton
public class Preferences
{
private Context context;
private SharedPreferences prefs;
@Inject
public Preferences()
{
this.context = HabitsApplication.getContext();

@ -32,20 +32,21 @@ import javax.inject.*;
import static org.isoron.uhabits.utils.DateUtils.*;
@Singleton
public class ReminderScheduler
{
private final IntentFactory intentFactory;
@Inject
protected PendingIntentFactory pendingIntentFactory;
private final IntentScheduler intentScheduler;
@Inject
protected IntentScheduler intentScheduler;
@Inject
HabitLogger logger;
public ReminderScheduler(IntentFactory intentFactory,
IntentScheduler intentScheduler)
@Inject
public ReminderScheduler()
{
this.intentFactory = intentFactory;
this.intentScheduler = intentScheduler;
HabitsApplication.getComponent().inject(this);
}
@ -56,7 +57,7 @@ public class ReminderScheduler
if (reminderTime == null) reminderTime = getReminderTime(reminder);
long timestamp = getStartOfDay(toLocalTime(reminderTime));
PendingIntent intent = intentFactory.buildShowReminder(habit,
PendingIntent intent = pendingIntentFactory.showReminder(habit,
reminderTime, timestamp);
intentScheduler.schedule(reminderTime, intent);
logger.logReminderScheduled(habit, reminderTime);

@ -22,17 +22,17 @@ package org.isoron.uhabits.utils;
import android.content.*;
import android.preference.*;
import org.isoron.uhabits.*;
import javax.inject.*;
@Singleton
public class WidgetPreferences
{
private Context context;
private SharedPreferences prefs;
public WidgetPreferences()
@Inject
public WidgetPreferences(Context context)
{
this.context = HabitsApplication.getContext();
prefs = PreferenceManager.getDefaultSharedPreferences(context);
}

@ -22,7 +22,7 @@
style="@style/dialogForm"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".ui.habits.edit.BaseDialogFragment"
tools:context=".ui.habits.edit.BaseDialog"
tools:ignore="MergeRootFrame">
<LinearLayout

@ -19,7 +19,10 @@
package org.isoron.uhabits;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.io.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.common.dialogs.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
@ -36,21 +39,41 @@ public class BaseUnitTest
@Inject
protected ModelFactory modelFactory;
@Inject
protected DialogFactory dialogFactory;
@Inject
protected IntentFactory intentFactory;
@Inject
protected HabitList habitList;
@Inject
protected HabitLogger logger;
@Inject
protected PendingIntentFactory pendingIntentFactory;
@Inject
protected IntentScheduler intentScheduler;
@Inject
protected DirFinder dirFinder;
protected TestComponent testComponent;
protected HabitFixtures fixtures;
public void log(String format, Object... args)
{
System.out.println(String.format(format, args));
}
@Before
public void setUp()
{
DateUtils.setFixedLocalTime(FIXED_LOCAL_TIME);
testComponent = DaggerTestComponent.builder().build();
testComponent = DaggerTestComponent.create();
HabitsApplication.setComponent(testComponent);
testComponent.inject(this);
fixtures = new HabitFixtures(habitList);
@ -63,9 +86,4 @@ public class BaseUnitTest
fixtures.purgeHabits();
}
public void log(String format, Object... args)
{
System.out.println(String.format(format, args));
}
}

@ -20,8 +20,11 @@
package org.isoron.uhabits;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.io.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.models.memory.*;
import org.isoron.uhabits.ui.common.dialogs.*;
import org.isoron.uhabits.utils.*;
import javax.inject.*;
@ -40,6 +43,20 @@ public class TestModule
return mock(CommandRunner.class);
}
@Provides
@Singleton
DialogFactory provideDialogFactory()
{
return mock(DialogFactory.class);
}
@Provides
@Singleton
DirFinder provideDirFinder()
{
return mock(DirFinder.class);
}
@Provides
Habit provideHabit()
{
@ -53,6 +70,27 @@ public class TestModule
return new MemoryHabitList();
}
@Provides
@Singleton
IntentFactory provideIntentFactory()
{
return mock(IntentFactory.class);
}
@Provides
@Singleton
IntentScheduler provideIntentScheduler()
{
return mock(IntentScheduler.class);
}
@Provides
@Singleton
HabitLogger provideLogger()
{
return mock(HabitLogger.class);
}
@Provides
@Singleton
ModelFactory provideModelFactory()
@ -62,16 +100,16 @@ public class TestModule
@Singleton
@Provides
Preferences providePreferences()
PendingIntentFactory providePendingIntentFactory()
{
return mock(Preferences.class);
return mock(PendingIntentFactory.class);
}
@Provides
@Singleton
WidgetPreferences provideWidgetPreferences()
@Provides
Preferences providePreferences()
{
return mock(WidgetPreferences.class);
return mock(Preferences.class);
}
@Provides
@ -83,8 +121,8 @@ public class TestModule
@Provides
@Singleton
HabitLogger provideLogger()
WidgetPreferences provideWidgetPreferences()
{
return mock(HabitLogger.class);
return mock(WidgetPreferences.class);
}
}

@ -0,0 +1,213 @@
/*
* Copyright (C) 2016 Á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.content.*;
import android.support.v7.app.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.*;
import org.isoron.uhabits.ui.common.dialogs.*;
import org.isoron.uhabits.ui.common.dialogs.ColorPickerDialog.*;
import org.isoron.uhabits.ui.habits.edit.*;
import org.junit.*;
import org.junit.runner.*;
import org.junit.runners.*;
import java.io.*;
import static org.mockito.Mockito.*;
@RunWith(JUnit4.class)
public class ListHabitsScreenTest extends BaseUnitTest
{
private BaseActivity activity;
private ListHabitsRootView rootView;
private ListHabitsScreen screen;
private ListHabitsController controller;
private Habit habit;
private Intent intent;
@Before
@Override
public void setUp()
{
super.setUp();
activity = mock(BaseActivity.class);
rootView = mock(ListHabitsRootView.class);
controller = mock(ListHabitsController.class);
intent = mock(Intent.class);
habit = new Habit();
screen = new ListHabitsScreen(activity, rootView);
screen.setController(controller);
}
@Test
public void testCreateHabitScreen()
{
CreateHabitDialog dialog = mock(CreateHabitDialog.class);
when(dialogFactory.buildCreateHabitDialog()).thenReturn(dialog);
screen.showCreateHabitScreen();
verify(activity).showDialog(eq(dialog), any());
}
@Test
public void testOnResult_bugReport()
{
screen.onResult(0, HabitsApplication.RESULT_BUG_REPORT, null);
verify(controller).onSendBugReport();
}
@Test
public void testOnResult_exportCSV()
{
screen.onResult(0, HabitsApplication.RESULT_EXPORT_CSV, null);
verify(controller).onExportCSV();
}
@Test
public void testOnResult_exportDB()
{
screen.onResult(0, HabitsApplication.RESULT_EXPORT_DB, null);
verify(controller).onExportDB();
}
@Test
public void testOnResult_importData()
{
screen.onResult(0, HabitsApplication.RESULT_IMPORT_DATA, null);
testShowImportScreen();
}
@Test
public void testShowAboutScreen() throws Exception
{
when(intentFactory.startAboutActivity(activity)).thenReturn(intent);
screen.showAboutScreen();
verify(activity).startActivity(eq(intent));
}
@Test
public void testShowColorPicker()
{
habit.setColor(999);
ColorPickerDialog picker = mock(ColorPickerDialog.class);
when(dialogFactory.buildColorPicker(999)).thenReturn(picker);
OnColorSelectedListener callback = mock(OnColorSelectedListener.class);
screen.showColorPicker(habit, callback);
verify(activity).showDialog(eq(picker), any());
verify(picker).setListener(callback);
}
@Test
public void testShowDeleteConfirmationScreen()
{
ConfirmDeleteDialog.Callback callback;
callback = mock(ConfirmDeleteDialog.Callback.class);
ConfirmDeleteDialog dialog = mock(ConfirmDeleteDialog.class);
when(dialogFactory.buildConfirmDeleteDialog(activity,
callback)).thenReturn(dialog);
screen.showDeleteConfirmationScreen(callback);
verify(activity).showDialog(dialog);
}
@Test
public void testShowEditHabitScreen()
{
EditHabitDialog dialog = mock(EditHabitDialog.class);
when(dialogFactory.buildEditHabitDialog(habit)).thenReturn(dialog);
screen.showEditHabitScreen(habit);
verify(activity).showDialog(eq(dialog), any());
}
@Test
public void testShowFAQScreen()
{
when(intentFactory.viewFAQ(activity)).thenReturn(intent);
screen.showFAQScreen();
verify(activity).startActivity(intent);
}
@Test
public void testShowHabitScreen()
{
when(intentFactory.startShowHabitActivity(activity, habit)).thenReturn(
intent);
screen.showHabitScreen(habit);
verify(activity).startActivity(intent);
}
@Test
public void testShowImportScreen()
{
File dir = mock(File.class);
when(dirFinder.findStorageDir(any())).thenReturn(dir);
FilePickerDialog picker = mock(FilePickerDialog.class);
AppCompatDialog dialog = mock(AppCompatDialog.class);
when(picker.getDialog()).thenReturn(dialog);
when(dialogFactory.buildFilePicker(activity, dir)).thenReturn(picker);
screen.showImportScreen();
verify(activity).showDialog(dialog);
}
@Test
public void testShowImportScreen_withInvalidPath()
{
when(dirFinder.findStorageDir(any())).thenReturn(null);
screen.showImportScreen();
verify(activity).showMessage(R.string.could_not_import);
}
@Test
public void testShowIntroScreen()
{
when(intentFactory.startIntroActivity(activity)).thenReturn(intent);
screen.showIntroScreen();
verify(activity).startActivity(intent);
}
@Test
public void testShowSettingsScreen()
{
when(intentFactory.startSettingsActivity(activity)).thenReturn(intent);
screen.showSettingsScreen();
verify(activity).startActivityForResult(eq(intent), anyInt());
}
}

@ -22,7 +22,6 @@ package org.isoron.uhabits.utils;
import android.app.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.models.*;
import org.junit.*;
@ -31,26 +30,19 @@ import static org.mockito.Mockito.*;
@SuppressWarnings("JavaDoc")
public class ReminderSchedulerTest extends BaseUnitTest
{
private IntentFactory intentFactory;
private IntentScheduler intentScheduler;
private ReminderScheduler scheduler;
private Habit habit;
private PendingIntent intent;
private ReminderScheduler reminderScheduler;
@Before
@Override
public void setUp()
{
super.setUp();
intentFactory = mock(IntentFactory.class);
intentScheduler = mock(IntentScheduler.class);
intent = mock(PendingIntent.class);
scheduler = new ReminderScheduler(intentFactory, intentScheduler);
reminderScheduler = new ReminderScheduler();
habit = fixtures.createEmptyHabit();
}
@ -94,7 +86,7 @@ public class ReminderSchedulerTest extends BaseUnitTest
fixtures.createEmptyHabit();
scheduler.schedule(habitList);
reminderScheduler.schedule(habitList);
verify(intentScheduler).schedule(1422347400000L, null);
verify(intentScheduler).schedule(1422297000000L, null);
@ -117,7 +109,7 @@ public class ReminderSchedulerTest extends BaseUnitTest
@Test
public void testSchedule_withoutReminder()
{
scheduler.schedule(habit, null);
reminderScheduler.schedule(habit, null);
verifyZeroInteractions(intentScheduler);
}
@ -125,15 +117,15 @@ public class ReminderSchedulerTest extends BaseUnitTest
long expectedCheckmarkTime,
long expectedReminderTime)
{
when(intentFactory.buildShowReminder(habit, expectedReminderTime,
expectedCheckmarkTime)).thenReturn(intent);
when(pendingIntentFactory.showReminder(habit, expectedReminderTime,
expectedCheckmarkTime)).thenReturn(intent);
scheduler.schedule(habit, atTime);
reminderScheduler.schedule(habit, atTime);
verify(logger).logReminderScheduled(habit, expectedReminderTime);
verify(intentFactory).buildShowReminder(habit, expectedReminderTime,
expectedCheckmarkTime);
verify(pendingIntentFactory).showReminder(habit, expectedReminderTime,
expectedCheckmarkTime);
verify(intentScheduler).schedule(expectedReminderTime, intent);
}
}

Loading…
Cancel
Save