mirror of https://github.com/iSoron/uhabits.git
parent
3ffa079e24
commit
7e8a2a0c1c
After Width: | Height: | Size: 551 B |
After Width: | Height: | Size: 505 B |
After Width: | Height: | Size: 559 B |
After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {AndroidModule.class})
|
||||
public interface AndroidTestComponent extends BaseComponent
|
||||
{
|
||||
void inject(BaseAndroidTest baseAndroidTest);
|
||||
}
|
||||
|
@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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.unit.ui.habits.list.view;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.models.Checkmark;
|
||||
import org.isoron.uhabits.ui.habits.list.views.CheckmarkButtonView;
|
||||
import org.isoron.uhabits.unit.views.ViewTest;
|
||||
import org.isoron.uhabits.utils.ColorUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class CheckmarkButtonViewTest extends ViewTest
|
||||
{
|
||||
public static final String PATH = "ui/habits/list/CheckmarkButtonView/";
|
||||
|
||||
private CountDownLatch latch;
|
||||
private CheckmarkButtonView view;
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
super.setUp();
|
||||
setSimilarityCutoff(0.03f);
|
||||
|
||||
latch = new CountDownLatch(1);
|
||||
view = new CheckmarkButtonView(targetContext);
|
||||
view.setValue(Checkmark.UNCHECKED);
|
||||
view.setColor(ColorUtils.CSV_PALETTE[7]);
|
||||
|
||||
measureView(dpToPixels(40), dpToPixels(40), view);
|
||||
}
|
||||
|
||||
protected void assertRendersCheckedExplicitly() throws IOException
|
||||
{
|
||||
assertRenders(view, PATH + "render_explicit_check.png");
|
||||
}
|
||||
|
||||
protected void assertRendersUnchecked() throws IOException
|
||||
{
|
||||
assertRenders(view, PATH + "render_unchecked.png");
|
||||
}
|
||||
|
||||
protected void assertRendersCheckedImplicitly() throws IOException
|
||||
{
|
||||
assertRenders(view, PATH + "render_implicit_check.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_unchecked() throws Exception
|
||||
{
|
||||
view.setValue(Checkmark.UNCHECKED);
|
||||
assertRendersUnchecked();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_explicitCheck() throws Exception
|
||||
{
|
||||
view.setValue(Checkmark.CHECKED_EXPLICITLY);
|
||||
assertRendersCheckedExplicitly();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_implicitCheck() throws Exception
|
||||
{
|
||||
view.setValue(Checkmark.CHECKED_IMPLICITLY);
|
||||
assertRendersCheckedImplicitly();
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testLongClick() throws Exception
|
||||
// {
|
||||
// setOnToggleListener();
|
||||
// view.performLongClick();
|
||||
// waitForLatch();
|
||||
// assertRendersCheckedExplicitly();
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testClick_withShortToggle_fromUnchecked() throws Exception
|
||||
// {
|
||||
// Preferences.getInstance().setShortToggleEnabled(true);
|
||||
// view.setValue(Checkmark.UNCHECKED);
|
||||
// setOnToggleListenerAndPerformClick();
|
||||
// assertRendersCheckedExplicitly();
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testClick_withShortToggle_fromChecked() throws Exception
|
||||
// {
|
||||
// Preferences.getInstance().setShortToggleEnabled(true);
|
||||
// view.setValue(Checkmark.CHECKED_EXPLICITLY);
|
||||
// setOnToggleListenerAndPerformClick();
|
||||
// assertRendersUnchecked();
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testClick_withShortToggle_withoutListener() throws Exception
|
||||
// {
|
||||
// Preferences.getInstance().setShortToggleEnabled(true);
|
||||
// view.setValue(Checkmark.CHECKED_EXPLICITLY);
|
||||
// view.setController(null);
|
||||
// view.performClick();
|
||||
// assertRendersUnchecked();
|
||||
// }
|
||||
//
|
||||
// protected void setOnToggleListenerAndPerformClick() throws InterruptedException
|
||||
// {
|
||||
// setOnToggleListener();
|
||||
// view.performClick();
|
||||
// waitForLatch();
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testClick_withoutShortToggle() throws Exception
|
||||
// {
|
||||
// Preferences.getInstance().setShortToggleEnabled(false);
|
||||
// setOnInvalidToggleListener();
|
||||
// view.performClick();
|
||||
// waitForLatch();
|
||||
// assertRendersUnchecked();
|
||||
// }
|
||||
|
||||
// protected void setOnInvalidToggleListener()
|
||||
// {
|
||||
// view.setController(new CheckmarkButtonView.Controller()
|
||||
// {
|
||||
// @Override
|
||||
// public void onToggleCheckmark(CheckmarkButtonView view, long timestamp)
|
||||
// {
|
||||
// fail();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onInvalidToggle(CheckmarkButtonView v)
|
||||
// {
|
||||
// assertThat(v, equalTo(view));
|
||||
// latch.countDown();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// protected void setOnToggleListener()
|
||||
// {
|
||||
// view.setController(new CheckmarkButtonView.Controller()
|
||||
// {
|
||||
// @Override
|
||||
// public void onToggleCheckmark(CheckmarkButtonView v, long t)
|
||||
// {
|
||||
// assertThat(v, equalTo(view));
|
||||
// assertThat(t, equalTo(DateUtils.getStartOfToday()));
|
||||
// latch.countDown();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onInvalidToggle(CheckmarkButtonView view)
|
||||
// {
|
||||
// fail();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.unit.ui.habits.list.view;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.models.Checkmark;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.ui.habits.list.views.CheckmarkPanelView;
|
||||
import org.isoron.uhabits.unit.views.ViewTest;
|
||||
import org.isoron.uhabits.utils.ColorUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class CheckmarkPanelViewTest extends ViewTest
|
||||
{
|
||||
public static final String PATH = "ui/habits/list/CheckmarkPanelView/";
|
||||
|
||||
private CountDownLatch latch;
|
||||
private CheckmarkPanelView view;
|
||||
private int checkmarks[];
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
super.setUp();
|
||||
setSimilarityCutoff(0.03f);
|
||||
prefs.setShouldReverseCheckmarks(false);
|
||||
|
||||
Habit habit = new Habit();
|
||||
|
||||
latch = new CountDownLatch(1);
|
||||
checkmarks = new int[]{Checkmark.CHECKED_EXPLICITLY, Checkmark.UNCHECKED,
|
||||
Checkmark.CHECKED_IMPLICITLY, Checkmark.CHECKED_EXPLICITLY};
|
||||
|
||||
view = new CheckmarkPanelView(targetContext);
|
||||
view.setHabit(habit);
|
||||
view.setCheckmarkValues(checkmarks);
|
||||
view.setColor(ColorUtils.CSV_PALETTE[7]);
|
||||
|
||||
measureView(dpToPixels(200), dpToPixels(200), view);
|
||||
}
|
||||
|
||||
// protected void waitForLatch() throws InterruptedException
|
||||
// {
|
||||
// assertTrue("Latch timeout", latch.await(1, TimeUnit.SECONDS));
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void testRender() throws Exception
|
||||
{
|
||||
assertRenders(view, PATH + "render.png");
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testToggleCheckmark_withLeftToRight() throws Exception
|
||||
// {
|
||||
// setToggleListener();
|
||||
// view.getButton(1).performToggle();
|
||||
// waitForLatch();
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testToggleCheckmark_withReverseCheckmarks() throws Exception
|
||||
// {
|
||||
// prefs.setShouldReverseCheckmarks(true);
|
||||
// view.setCheckmarkValues(checkmarks); // refresh after preference change
|
||||
//
|
||||
// setToggleListener();
|
||||
// view.getButton(2).performToggle();
|
||||
// waitForLatch();
|
||||
// }
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {AndroidModule.class})
|
||||
public interface AndroidComponent extends BaseComponent
|
||||
{
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.isoron.uhabits.commands.CommandRunner;
|
||||
import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache;
|
||||
import org.isoron.uhabits.utils.Preferences;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class AndroidModule
|
||||
{
|
||||
@Provides
|
||||
@Singleton
|
||||
Preferences providePreferences()
|
||||
{
|
||||
return new Preferences();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CommandRunner provideCommandRunner()
|
||||
{
|
||||
return new CommandRunner();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
HabitCardListCache provideHabitCardListCache()
|
||||
{
|
||||
return new HabitCardListCache();
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.isoron.uhabits.tasks.ToggleRepetitionTask;
|
||||
import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment;
|
||||
import org.isoron.uhabits.ui.habits.list.ListHabitsSelectionMenu;
|
||||
import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter;
|
||||
import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache;
|
||||
import org.isoron.uhabits.ui.habits.list.ListHabitsController;
|
||||
import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController;
|
||||
import org.isoron.uhabits.ui.habits.list.model.HintList;
|
||||
import org.isoron.uhabits.ui.habits.list.views.CheckmarkPanelView;
|
||||
|
||||
public interface BaseComponent
|
||||
{
|
||||
void inject(CheckmarkButtonController checkmarkButtonController);
|
||||
|
||||
void inject(ListHabitsController listHabitsController);
|
||||
|
||||
void inject(CheckmarkPanelView checkmarkPanelView);
|
||||
|
||||
void inject(ToggleRepetitionTask toggleRepetitionTask);
|
||||
|
||||
void inject(BaseDialogFragment baseDialogFragment);
|
||||
|
||||
void inject(HabitCardListCache habitCardListCache);
|
||||
|
||||
void inject(HabitBroadcastReceiver habitBroadcastReceiver);
|
||||
|
||||
void inject(ListHabitsSelectionMenu listHabitsSelectionMenu);
|
||||
|
||||
void inject(HintList hintList);
|
||||
|
||||
void inject(HabitCardListAdapter habitCardListAdapter);
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.tasks.ExportCSVTask;
|
||||
import org.isoron.uhabits.tasks.ExportDBTask;
|
||||
import org.isoron.uhabits.tasks.ImportDataTask;
|
||||
import org.isoron.uhabits.tasks.ProgressBar;
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
import org.isoron.uhabits.utils.Preferences;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class MainController implements ImportDataTask.Listener, ExportCSVTask.Listener,
|
||||
ExportDBTask.Listener
|
||||
{
|
||||
public interface Screen
|
||||
{
|
||||
void showIntroScreen();
|
||||
|
||||
void showMessage(Integer stringId);
|
||||
|
||||
void refresh(Long refreshKey);
|
||||
|
||||
void sendFile(String filename);
|
||||
|
||||
void sendEmail(String to, String subject, String content);
|
||||
|
||||
ProgressBar getProgressBar();
|
||||
}
|
||||
|
||||
public interface System
|
||||
{
|
||||
void scheduleReminders();
|
||||
|
||||
void updateWidgets();
|
||||
|
||||
File dumpBugReportToFile() throws IOException;
|
||||
|
||||
String getBugReport() throws IOException;
|
||||
}
|
||||
|
||||
System sys;
|
||||
Screen screen;
|
||||
Preferences prefs;
|
||||
|
||||
public MainController()
|
||||
{
|
||||
prefs = Preferences.getInstance();
|
||||
}
|
||||
|
||||
public void setScreen(Screen screen)
|
||||
{
|
||||
this.screen = screen;
|
||||
}
|
||||
|
||||
public void setSystem(System sys)
|
||||
{
|
||||
this.sys = sys;
|
||||
}
|
||||
|
||||
public void onStartup()
|
||||
{
|
||||
prefs.initialize();
|
||||
prefs.incrementLaunchCount();
|
||||
prefs.updateLastAppVersion();
|
||||
if(prefs.isFirstRun()) onFirstRun();
|
||||
|
||||
sys.updateWidgets();
|
||||
sys.scheduleReminders();
|
||||
}
|
||||
|
||||
private void onFirstRun()
|
||||
{
|
||||
prefs.setFirstRun(false);
|
||||
prefs.setLastHintTimestamp(DateUtils.getStartOfToday());
|
||||
screen.showIntroScreen();
|
||||
}
|
||||
|
||||
public void importData(File file)
|
||||
{
|
||||
ImportDataTask task = new ImportDataTask(file, screen.getProgressBar());
|
||||
task.setListener(this);
|
||||
task.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImportDataFinished(int result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case ImportDataTask.SUCCESS:
|
||||
screen.refresh(null);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public void exportCSV()
|
||||
{
|
||||
ExportCSVTask task = new ExportCSVTask(Habit.getAll(true), screen.getProgressBar());
|
||||
task.setListener(this);
|
||||
task.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExportCSVFinished(String filename)
|
||||
{
|
||||
if(filename != null) screen.sendFile(filename);
|
||||
else screen.showMessage(R.string.could_not_export);
|
||||
}
|
||||
|
||||
public void exportDB()
|
||||
{
|
||||
ExportDBTask task = new ExportDBTask(screen.getProgressBar());
|
||||
task.setListener(this);
|
||||
task.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExportDBFinished(String filename)
|
||||
{
|
||||
if(filename != null) screen.sendFile(filename);
|
||||
else screen.showMessage(R.string.could_not_export);
|
||||
}
|
||||
|
||||
public void sendBugReport()
|
||||
{
|
||||
try
|
||||
{
|
||||
sys.dumpBugReportToFile();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
String log = "---------- BUG REPORT BEGINS ----------\n";
|
||||
log += sys.getBugReport();
|
||||
log += "---------- BUG REPORT ENDS ------------\n";
|
||||
String to = "dev@loophabits.org";
|
||||
String subject = "Bug Report - Loop Habit Tracker";
|
||||
screen.sendEmail(log, to, subject);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
screen.showMessage(R.string.bug_report_failed);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
public abstract class BaseMenu
|
||||
{
|
||||
private final BaseActivity activity;
|
||||
|
||||
public BaseMenu(BaseActivity activity)
|
||||
{
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
public final boolean onCreate(@NonNull MenuInflater inflater,
|
||||
@NonNull Menu menu)
|
||||
{
|
||||
menu.clear();
|
||||
inflater.inflate(getMenuResourceId(), menu);
|
||||
onCreate(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onCreate(@NonNull Menu menu)
|
||||
{
|
||||
}
|
||||
|
||||
public boolean onItemSelected(@NonNull MenuItem item)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract int getMenuResourceId();
|
||||
|
||||
public void invalidate()
|
||||
{
|
||||
activity.invalidateOptionsMenu();
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.utils.InterfaceUtils;
|
||||
|
||||
public abstract class BaseRootView extends FrameLayout
|
||||
{
|
||||
private final Context context;
|
||||
|
||||
public BaseRootView(Context context)
|
||||
{
|
||||
super(context);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public BaseRootView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public BaseRootView(Context context, AttributeSet attrs, int defStyleAttr)
|
||||
{
|
||||
super(context, attrs, defStyleAttr);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public BaseRootView(Context context,
|
||||
AttributeSet attrs,
|
||||
int defStyleAttr,
|
||||
int defStyleRes)
|
||||
{
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public boolean getDisplayHomeAsUp()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ProgressBar getProgressBar()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public abstract Toolbar getToolbar();
|
||||
|
||||
public int getToolbarColor()
|
||||
{
|
||||
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
||||
// {
|
||||
// if (InterfaceUtils.isNightMode()) return;
|
||||
// int color = activity.getResources().getColor(R.color.grey_900);
|
||||
// }
|
||||
// if (!InterfaceUtils.getStyledBoolean(activity, R.attr.useHabitColorAsPrimary)) return;
|
||||
return Color.BLACK;
|
||||
}
|
||||
|
||||
private void hideFakeToolbarShadow()
|
||||
{
|
||||
View view = findViewById(R.id.toolbarShadow);
|
||||
if (view != null) view.setVisibility(View.GONE);
|
||||
|
||||
view = findViewById(R.id.headerShadow);
|
||||
if (view != null) view.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
protected void initToolbar()
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
getToolbar().setElevation(InterfaceUtils.dpToPixels(context, 2));
|
||||
|
||||
hideFakeToolbarShadow();
|
||||
}
|
||||
}
|
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatDialogFragment;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.isoron.uhabits.tasks.ProgressBar;
|
||||
import org.isoron.uhabits.utils.ColorUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public abstract class BaseScreen
|
||||
{
|
||||
protected BaseActivity activity;
|
||||
|
||||
private Toast toast;
|
||||
|
||||
@Nullable
|
||||
private BaseRootView rootView;
|
||||
|
||||
@Nullable
|
||||
private BaseSelectionMenu selectionMenu;
|
||||
|
||||
public BaseScreen(BaseActivity activity)
|
||||
{
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
public void finishSelection()
|
||||
{
|
||||
if (selectionMenu == null) return;
|
||||
selectionMenu.finish();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ProgressBar getProgressBar()
|
||||
{
|
||||
if (rootView == null) return null;
|
||||
return new ProgressBarWrapper(rootView.getProgressBar());
|
||||
}
|
||||
|
||||
public void invalidate()
|
||||
{
|
||||
if (rootView == null) return;
|
||||
rootView.invalidate();
|
||||
}
|
||||
|
||||
public void onResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
}
|
||||
|
||||
public void setMenu(@Nullable BaseMenu menu)
|
||||
{
|
||||
activity.setBaseMenu(menu);
|
||||
}
|
||||
|
||||
public void setRootView(@Nullable BaseRootView rootView)
|
||||
{
|
||||
this.rootView = rootView;
|
||||
activity.setContentView(rootView);
|
||||
if (rootView == null) return;
|
||||
|
||||
initToolbar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the menu to be shown when a selection is active on the screen.
|
||||
*
|
||||
* @param menu the menu to be shown during a selection
|
||||
*/
|
||||
public void setSelectionMenu(@Nullable BaseSelectionMenu menu)
|
||||
{
|
||||
this.selectionMenu = menu;
|
||||
}
|
||||
|
||||
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();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.setType("message/rfc822");
|
||||
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{to});
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, content);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
|
||||
public void showSendFileScreen(@NonNull String archiveFilename)
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.setType("application/zip");
|
||||
intent.putExtra(Intent.EXTRA_STREAM,
|
||||
Uri.fromFile(new File(archiveFilename)));
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the screen to start a selection. If a selection menu was
|
||||
* provided, this menu will be shown instead of the regular one.
|
||||
*/
|
||||
public void startSelection()
|
||||
{
|
||||
activity.startSupportActionMode(new ActionModeWrapper());
|
||||
}
|
||||
|
||||
private void initToolbar()
|
||||
{
|
||||
if (rootView == null) return;
|
||||
|
||||
Toolbar toolbar = rootView.getToolbar();
|
||||
activity.setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = activity.getSupportActionBar();
|
||||
if (actionBar == null) return;
|
||||
|
||||
actionBar.setDisplayHomeAsUpEnabled(rootView.getDisplayHomeAsUp());
|
||||
|
||||
int color = rootView.getToolbarColor();
|
||||
setActionBarColor(actionBar, color);
|
||||
setStatusBarColor(color);
|
||||
}
|
||||
|
||||
private void setActionBarColor(@NonNull ActionBar actionBar, int color)
|
||||
{
|
||||
ColorDrawable drawable = new ColorDrawable(color);
|
||||
actionBar.setBackgroundDrawable(drawable);
|
||||
}
|
||||
|
||||
private void setStatusBarColor(int baseColor)
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
{
|
||||
int darkerColor =
|
||||
ColorUtils.mixColors(baseColor, Color.BLACK, 0.75f);
|
||||
activity.getWindow().setStatusBarColor(darkerColor);
|
||||
}
|
||||
}
|
||||
|
||||
protected void showDialog(AppCompatDialogFragment dialog, String tag)
|
||||
{
|
||||
dialog.show(activity.getSupportFragmentManager(), tag);
|
||||
}
|
||||
|
||||
private class ActionModeWrapper implements ActionMode.Callback
|
||||
{
|
||||
@Override
|
||||
public boolean onActionItemClicked(@Nullable ActionMode mode,
|
||||
@Nullable MenuItem item)
|
||||
{
|
||||
if (item == null || selectionMenu == null) return false;
|
||||
return selectionMenu.onItemClicked(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(@Nullable ActionMode mode,
|
||||
@Nullable Menu menu)
|
||||
{
|
||||
if (selectionMenu == null) return false;
|
||||
if (mode == null || menu == null) return false;
|
||||
selectionMenu.onCreate(activity.getMenuInflater(), mode, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(@Nullable ActionMode mode)
|
||||
{
|
||||
if (selectionMenu == null) return;
|
||||
selectionMenu.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(@Nullable ActionMode mode,
|
||||
@Nullable Menu menu)
|
||||
{
|
||||
if (selectionMenu == null || menu == null) return false;
|
||||
return selectionMenu.onPrepare(menu);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
public abstract class BaseSelectionMenu
|
||||
{
|
||||
@Nullable
|
||||
private ActionMode actionMode;
|
||||
|
||||
/**
|
||||
* Finishes the selection operation.
|
||||
*/
|
||||
public void finish()
|
||||
{
|
||||
if (actionMode != null) actionMode.finish();
|
||||
}
|
||||
|
||||
public void invalidate()
|
||||
{
|
||||
if (actionMode != null) actionMode.invalidate();
|
||||
}
|
||||
|
||||
public final void onCreate(@NonNull MenuInflater menuInflater,
|
||||
@NonNull ActionMode mode,
|
||||
@NonNull Menu menu)
|
||||
{
|
||||
this.actionMode = mode;
|
||||
menuInflater.inflate(getResourceId(), menu);
|
||||
onCreate(menu);
|
||||
}
|
||||
|
||||
public void onDestroy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public boolean onItemClicked(@NonNull MenuItem item)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onPrepare(@NonNull Menu menu)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setTitle(String title)
|
||||
{
|
||||
if (actionMode != null) actionMode.setTitle(title);
|
||||
}
|
||||
|
||||
protected abstract int getResourceId();
|
||||
|
||||
/**
|
||||
* Called when the menu is first created, right after the menu has been
|
||||
* inflated.
|
||||
*
|
||||
* @param menu the menu containing the buttons
|
||||
*/
|
||||
protected void onCreate(@NonNull Menu menu)
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.isoron.uhabits.BuildConfig;
|
||||
import org.isoron.uhabits.tasks.BaseTask;
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
import org.isoron.uhabits.utils.FileUtils;
|
||||
import org.isoron.uhabits.utils.ReminderUtils;
|
||||
import org.isoron.uhabits.widgets.WidgetManager;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class BaseSystem
|
||||
{
|
||||
private Context context;
|
||||
|
||||
public BaseSystem(Context context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public String getLogcat() throws IOException
|
||||
{
|
||||
int maxNLines = 250;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
String[] command = new String[]{"logcat", "-d"};
|
||||
Process process = Runtime.getRuntime().exec(command);
|
||||
|
||||
InputStreamReader in = new InputStreamReader(process.getInputStream());
|
||||
BufferedReader bufferedReader = new BufferedReader(in);
|
||||
|
||||
LinkedList<String> log = new LinkedList<>();
|
||||
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null)
|
||||
{
|
||||
log.addLast(line);
|
||||
if (log.size() > maxNLines) log.removeFirst();
|
||||
}
|
||||
|
||||
for (String l : log)
|
||||
{
|
||||
builder.append(l);
|
||||
builder.append('\n');
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public String getDeviceInfo()
|
||||
{
|
||||
if (context == null) return "";
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
WindowManager wm =
|
||||
(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
|
||||
b.append(
|
||||
String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME));
|
||||
b.append(
|
||||
String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE));
|
||||
b.append(String.format("OS Version: %s (%s)\n",
|
||||
java.lang.System.getProperty("os.version"),
|
||||
android.os.Build.VERSION.INCREMENTAL));
|
||||
b.append(
|
||||
String.format("OS API Level: %s\n", android.os.Build.VERSION.SDK));
|
||||
b.append(String.format("Device: %s\n", android.os.Build.DEVICE));
|
||||
b.append(
|
||||
String.format("Model (Product): %s (%s)\n", android.os.Build.MODEL,
|
||||
android.os.Build.PRODUCT));
|
||||
b.append(
|
||||
String.format("Manufacturer: %s\n", android.os.Build.MANUFACTURER));
|
||||
b.append(String.format("Other tags: %s\n", android.os.Build.TAGS));
|
||||
b.append(String.format("Screen Width: %s\n",
|
||||
wm.getDefaultDisplay().getWidth()));
|
||||
b.append(String.format("Screen Height: %s\n",
|
||||
wm.getDefaultDisplay().getHeight()));
|
||||
b.append(String.format("SD Card state: %s\n\n",
|
||||
Environment.getExternalStorageState()));
|
||||
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public File dumpBugReportToFile() throws IOException
|
||||
{
|
||||
String date =
|
||||
DateUtils.getBackupDateFormat().format(DateUtils.getLocalTime());
|
||||
|
||||
if (context == null) throw new RuntimeException(
|
||||
"application context should not be null");
|
||||
File dir = FileUtils.getFilesDir("Logs");
|
||||
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;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getBugReport() throws IOException
|
||||
{
|
||||
String logcat = getLogcat();
|
||||
String deviceInfo = getDeviceInfo();
|
||||
return deviceInfo + "\n" + logcat;
|
||||
}
|
||||
|
||||
public void scheduleReminders()
|
||||
{
|
||||
new BaseTask()
|
||||
{
|
||||
|
||||
@Override
|
||||
protected void doInBackground()
|
||||
{
|
||||
ReminderUtils.createReminderAlarms(context);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
public void updateWidgets()
|
||||
{
|
||||
new BaseTask()
|
||||
{
|
||||
|
||||
@Override
|
||||
protected void doInBackground()
|
||||
{
|
||||
WidgetManager.updateWidgets(context);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
|
||||
public class HintManager
|
||||
{
|
||||
private Context context;
|
||||
private SharedPreferences prefs;
|
||||
private View hintView;
|
||||
|
||||
public HintManager(Context context, View hintView)
|
||||
{
|
||||
this.context = context;
|
||||
this.hintView = hintView;
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
public void dismissHint()
|
||||
{
|
||||
hintView.animate().alpha(0f).setDuration(500).setListener(new AnimatorListenerAdapter()
|
||||
{
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation)
|
||||
{
|
||||
hintView.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void showHintIfAppropriate()
|
||||
{
|
||||
Integer lastHintNumber = prefs.getInt("last_hint_number", -1);
|
||||
Long lastHintTimestamp = prefs.getLong("last_hint_timestamp", -1);
|
||||
|
||||
if (DateUtils.getStartOfToday() > lastHintTimestamp) showHint(lastHintNumber + 1);
|
||||
}
|
||||
|
||||
private void showHint(int hintNumber)
|
||||
{
|
||||
String[] hints = context.getResources().getStringArray(R.array.hints);
|
||||
if (hintNumber >= hints.length) return;
|
||||
|
||||
prefs.edit().putInt("last_hint_number", hintNumber).apply();
|
||||
prefs.edit().putLong("last_hint_timestamp", DateUtils.getStartOfToday()).apply();
|
||||
|
||||
TextView tvContent = (TextView) hintView.findViewById(R.id.hintContent);
|
||||
tvContent.setText(hints[hintNumber]);
|
||||
|
||||
hintView.setAlpha(0.0f);
|
||||
hintView.setVisibility(View.VISIBLE);
|
||||
hintView.animate().alpha(1f).setDuration(500);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains classes for AboutActivity
|
||||
*/
|
||||
package org.isoron.uhabits.ui.about;
|
@ -1,99 +0,0 @@
|
||||
/*
|
||||
* 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.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class HabitListAdapter extends BaseAdapter
|
||||
{
|
||||
private LayoutInflater inflater;
|
||||
private HabitListLoader loader;
|
||||
private ListHabitsHelper helper;
|
||||
private List selectedPositions;
|
||||
private View.OnLongClickListener onCheckmarkLongClickListener;
|
||||
private View.OnClickListener onCheckmarkClickListener;
|
||||
|
||||
public HabitListAdapter(Context context, HabitListLoader loader)
|
||||
{
|
||||
this.loader = loader;
|
||||
|
||||
inflater = LayoutInflater.from(context);
|
||||
helper = new ListHabitsHelper(context, loader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount()
|
||||
{
|
||||
return loader.habits.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Habit getItem(int position)
|
||||
{
|
||||
return loader.habitsList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position)
|
||||
{
|
||||
return (getItem(position)).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent)
|
||||
{
|
||||
final Habit habit = loader.habitsList.get(position);
|
||||
boolean selected = selectedPositions.contains(position);
|
||||
|
||||
if (view == null || (Long) view.getTag(R.id.timestamp_key) != DateUtils.getStartOfToday())
|
||||
{
|
||||
view = helper.inflateHabitCard(inflater, onCheckmarkLongClickListener,
|
||||
onCheckmarkClickListener);
|
||||
}
|
||||
|
||||
helper.updateHabitCard(view, habit, selected);
|
||||
return view;
|
||||
}
|
||||
|
||||
public void setSelectedPositions(List selectedPositions)
|
||||
{
|
||||
this.selectedPositions = selectedPositions;
|
||||
}
|
||||
|
||||
public void setOnCheckmarkLongClickListener(View.OnLongClickListener listener)
|
||||
{
|
||||
this.onCheckmarkLongClickListener = listener;
|
||||
}
|
||||
|
||||
public void setOnCheckmarkClickListener(View.OnClickListener listener)
|
||||
{
|
||||
this.onCheckmarkClickListener = listener;
|
||||
}
|
||||
}
|
@ -1,223 +0,0 @@
|
||||
/*
|
||||
* 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.support.annotation.Nullable;
|
||||
|
||||
import org.isoron.uhabits.commands.Command;
|
||||
import org.isoron.uhabits.commands.CommandRunner;
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.tasks.BaseTask;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class HabitListLoader implements CommandRunner.Listener
|
||||
{
|
||||
public interface Listener
|
||||
{
|
||||
void onLoadFinished();
|
||||
}
|
||||
|
||||
private BaseTask currentFetchTask;
|
||||
private int checkmarkCount;
|
||||
|
||||
@Nullable
|
||||
private Listener listener;
|
||||
private Long lastLoadTimestamp;
|
||||
|
||||
public HashMap<Long, Habit> habits;
|
||||
public List<Habit> habitsList;
|
||||
public HashMap<Long, int[]> checkmarks;
|
||||
public HashMap<Long, Integer> scores;
|
||||
|
||||
boolean includeArchived;
|
||||
|
||||
public void setIncludeArchived(boolean includeArchived)
|
||||
{
|
||||
this.includeArchived = includeArchived;
|
||||
}
|
||||
|
||||
public void setCheckmarkCount(int checkmarkCount)
|
||||
{
|
||||
this.checkmarkCount = checkmarkCount;
|
||||
}
|
||||
|
||||
public void setListener(@Nullable Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public Long getLastLoadTimestamp()
|
||||
{
|
||||
return lastLoadTimestamp;
|
||||
}
|
||||
|
||||
public HabitListLoader()
|
||||
{
|
||||
habits = new HashMap<>();
|
||||
checkmarks = new HashMap<>();
|
||||
scores = new HashMap<>();
|
||||
}
|
||||
|
||||
public void reorder(int from, int to)
|
||||
{
|
||||
Habit fromHabit = habitsList.get(from);
|
||||
Habit toHabit = habitsList.get(to);
|
||||
|
||||
habitsList.remove(from);
|
||||
habitsList.add(to, fromHabit);
|
||||
|
||||
Habit.reorder(fromHabit, toHabit);
|
||||
}
|
||||
|
||||
public void updateAllHabits(final boolean updateScoresAndCheckmarks)
|
||||
{
|
||||
if (currentFetchTask != null) currentFetchTask.cancel(true);
|
||||
|
||||
currentFetchTask = new BaseTask()
|
||||
{
|
||||
public HashMap<Long, Habit> newHabits;
|
||||
public HashMap<Long, int[]> newCheckmarks;
|
||||
public HashMap<Long, Integer> newScores;
|
||||
public List<Habit> newHabitList;
|
||||
|
||||
@Override
|
||||
protected void doInBackground()
|
||||
{
|
||||
newHabits = new HashMap<>();
|
||||
newCheckmarks = new HashMap<>();
|
||||
newScores = new HashMap<>();
|
||||
newHabitList = Habit.getAll(includeArchived);
|
||||
|
||||
long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime());
|
||||
long dateFrom = dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay;
|
||||
int[] empty = new int[checkmarkCount];
|
||||
|
||||
for(Habit h : newHabitList)
|
||||
{
|
||||
Long id = h.getId();
|
||||
|
||||
newHabits.put(id, h);
|
||||
|
||||
if(checkmarks.containsKey(id))
|
||||
newCheckmarks.put(id, checkmarks.get(id));
|
||||
else
|
||||
newCheckmarks.put(id, empty);
|
||||
|
||||
if(scores.containsKey(id))
|
||||
newScores.put(id, scores.get(id));
|
||||
else
|
||||
newScores.put(id, 0);
|
||||
}
|
||||
|
||||
commit();
|
||||
|
||||
if(!updateScoresAndCheckmarks) return;
|
||||
|
||||
int current = 0;
|
||||
for (Habit h : newHabitList)
|
||||
{
|
||||
if (isCancelled()) return;
|
||||
|
||||
Long id = h.getId();
|
||||
newScores.put(id, h.scores.getTodayValue());
|
||||
newCheckmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo));
|
||||
|
||||
publishProgress(current++, newHabits.size());
|
||||
}
|
||||
}
|
||||
|
||||
private void commit()
|
||||
{
|
||||
habits = newHabits;
|
||||
scores = newScores;
|
||||
checkmarks = newCheckmarks;
|
||||
habitsList = newHabitList;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... values)
|
||||
{
|
||||
if(listener != null) listener.onLoadFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid)
|
||||
{
|
||||
if (isCancelled()) return;
|
||||
|
||||
lastLoadTimestamp = DateUtils.getStartOfToday();
|
||||
currentFetchTask = null;
|
||||
|
||||
if(listener != null) listener.onLoadFinished();
|
||||
super.onPostExecute(null);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
currentFetchTask.execute();
|
||||
}
|
||||
|
||||
public void updateHabit(final Long id)
|
||||
{
|
||||
new BaseTask()
|
||||
{
|
||||
@Override
|
||||
protected void doInBackground()
|
||||
{
|
||||
long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime());
|
||||
long dateFrom = dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay;
|
||||
|
||||
Habit h = Habit.get(id);
|
||||
if(h == null) return;
|
||||
|
||||
habits.put(id, h);
|
||||
scores.put(id, h.scores.getTodayValue());
|
||||
checkmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid)
|
||||
{
|
||||
if(listener != null) listener.onLoadFinished();
|
||||
super.onPostExecute(null);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
public void onResume()
|
||||
{
|
||||
CommandRunner.getInstance().addListener(this);
|
||||
}
|
||||
|
||||
public void onPause()
|
||||
{
|
||||
CommandRunner.getInstance().removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommandExecuted(Command command, Long refreshKey)
|
||||
{
|
||||
if(refreshKey == null) updateAllHabits(true);
|
||||
else updateHabit(refreshKey);
|
||||
}
|
||||
}
|
@ -1,229 +0,0 @@
|
||||
/*
|
||||
* 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.DialogInterface;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.android.colorpicker.ColorPickerDialog;
|
||||
import com.android.colorpicker.ColorPickerSwatch;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.commands.ArchiveHabitsCommand;
|
||||
import org.isoron.uhabits.commands.ChangeHabitColorCommand;
|
||||
import org.isoron.uhabits.commands.CommandRunner;
|
||||
import org.isoron.uhabits.commands.DeleteHabitsCommand;
|
||||
import org.isoron.uhabits.commands.UnarchiveHabitsCommand;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.ui.BaseActivity;
|
||||
import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment;
|
||||
import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment;
|
||||
import org.isoron.uhabits.utils.ColorUtils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class HabitListSelectionCallback implements ActionMode.Callback
|
||||
{
|
||||
private HabitListLoader loader;
|
||||
private List<Integer> selectedPositions;
|
||||
private BaseActivity activity;
|
||||
private Listener listener;
|
||||
|
||||
public interface Listener
|
||||
{
|
||||
void onActionModeDestroyed(ActionMode mode);
|
||||
}
|
||||
|
||||
public HabitListSelectionCallback(BaseActivity activity, HabitListLoader loader)
|
||||
{
|
||||
this.activity = activity;
|
||||
this.loader = loader;
|
||||
selectedPositions = new LinkedList<>();
|
||||
}
|
||||
|
||||
public void setListener(Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void setSelectedPositions(List<Integer> selectedPositions)
|
||||
{
|
||||
this.selectedPositions = selectedPositions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu)
|
||||
{
|
||||
activity.getMenuInflater().inflate(R.menu.list_habits_selection, menu);
|
||||
updateTitle(mode);
|
||||
updateActions(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu)
|
||||
{
|
||||
updateTitle(mode);
|
||||
updateActions(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateActions(Menu menu)
|
||||
{
|
||||
boolean showEdit = (selectedPositions.size() == 1);
|
||||
boolean showArchive = true;
|
||||
boolean showUnarchive = true;
|
||||
for (int i : selectedPositions)
|
||||
{
|
||||
Habit h = loader.habitsList.get(i);
|
||||
if (h.isArchived()) showArchive = false;
|
||||
else showUnarchive = false;
|
||||
}
|
||||
|
||||
MenuItem itemEdit = menu.findItem(R.id.action_edit_habit);
|
||||
MenuItem itemColor = menu.findItem(R.id.action_color);
|
||||
MenuItem itemArchive = menu.findItem(R.id.action_archive_habit);
|
||||
MenuItem itemUnarchive = menu.findItem(R.id.action_unarchive_habit);
|
||||
|
||||
itemColor.setVisible(true);
|
||||
itemEdit.setVisible(showEdit);
|
||||
itemArchive.setVisible(showArchive);
|
||||
itemUnarchive.setVisible(showUnarchive);
|
||||
}
|
||||
|
||||
private void updateTitle(ActionMode mode)
|
||||
{
|
||||
mode.setTitle("" + selectedPositions.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(final ActionMode mode, MenuItem item)
|
||||
{
|
||||
final LinkedList<Habit> selectedHabits = new LinkedList<>();
|
||||
for (int i : selectedPositions)
|
||||
selectedHabits.add(loader.habitsList.get(i));
|
||||
|
||||
Habit firstHabit = selectedHabits.getFirst();
|
||||
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.action_archive_habit:
|
||||
archiveHabits(selectedHabits);
|
||||
mode.finish();
|
||||
return true;
|
||||
|
||||
case R.id.action_unarchive_habit:
|
||||
unarchiveHabits(selectedHabits);
|
||||
mode.finish();
|
||||
return true;
|
||||
|
||||
case R.id.action_edit_habit:
|
||||
{
|
||||
editHabit(firstHabit);
|
||||
mode.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_color:
|
||||
{
|
||||
showColorPicker(mode, selectedHabits, firstHabit);
|
||||
mode.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_delete:
|
||||
{
|
||||
deleteHabits(mode, selectedHabits);
|
||||
mode.finish();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void deleteHabits(final ActionMode mode, final LinkedList<Habit> selectedHabits)
|
||||
{
|
||||
new AlertDialog.Builder(activity).setTitle(R.string.delete_habits)
|
||||
.setMessage(R.string.delete_habits_message)
|
||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
CommandRunner.getInstance()
|
||||
.execute(new DeleteHabitsCommand(selectedHabits), null);
|
||||
mode.finish();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showColorPicker(final ActionMode mode, final LinkedList<Habit> selectedHabits,
|
||||
Habit firstHabit)
|
||||
{
|
||||
int originalAndroidColor = ColorUtils.getColor(activity, firstHabit.color);
|
||||
|
||||
ColorPickerDialog picker =
|
||||
ColorPickerDialog.newInstance(R.string.color_picker_default_title,
|
||||
ColorUtils.getPalette(activity), originalAndroidColor, 4,
|
||||
ColorPickerDialog.SIZE_SMALL);
|
||||
|
||||
picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener()
|
||||
{
|
||||
public void onColorSelected(int androidColor)
|
||||
{
|
||||
int paletteColor = ColorUtils.colorToPaletteIndex(activity, androidColor);
|
||||
CommandRunner.getInstance()
|
||||
.execute(new ChangeHabitColorCommand(selectedHabits, paletteColor), null);
|
||||
mode.finish();
|
||||
}
|
||||
});
|
||||
picker.show(activity.getSupportFragmentManager(), "picker");
|
||||
}
|
||||
|
||||
private void editHabit(Habit habit)
|
||||
{
|
||||
BaseDialogFragment
|
||||
frag = EditHabitDialogFragment.newInstance(habit.getId());
|
||||
frag.show(activity.getSupportFragmentManager(), "editHabit");
|
||||
}
|
||||
|
||||
private void unarchiveHabits(LinkedList<Habit> selectedHabits)
|
||||
{
|
||||
CommandRunner.getInstance().execute(new UnarchiveHabitsCommand(selectedHabits), null);
|
||||
}
|
||||
|
||||
private void archiveHabits(LinkedList<Habit> selectedHabits)
|
||||
{
|
||||
CommandRunner.getInstance().execute(new ArchiveHabitsCommand(selectedHabits), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode)
|
||||
{
|
||||
if(listener != null) listener.onActionModeDestroyed(mode);
|
||||
}
|
||||
}
|
@ -1,273 +0,0 @@
|
||||
/*
|
||||
* 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.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import com.mobeta.android.dslv.DragSortController;
|
||||
import com.mobeta.android.dslv.DragSortListView;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.utils.Preferences;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class HabitListView extends DragSortListView implements View.OnClickListener,
|
||||
View.OnLongClickListener, DragSortListView.DropListener, AdapterView.OnItemClickListener,
|
||||
AdapterView.OnItemLongClickListener, DragSortListView.DragListener, HabitListLoader.Listener
|
||||
{
|
||||
private final HabitListLoader loader;
|
||||
private final HabitListAdapter adapter;
|
||||
private final ListHabitsHelper helper;
|
||||
private final Preferences prefs;
|
||||
private final List<Integer> selectedPositions;
|
||||
|
||||
@Nullable
|
||||
private Listener listener;
|
||||
private long lastLongClick;
|
||||
private boolean showArchived;
|
||||
|
||||
public HabitListView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
|
||||
loader = new HabitListLoader();
|
||||
adapter = new HabitListAdapter(context, loader);
|
||||
selectedPositions = new LinkedList<>();
|
||||
prefs = Preferences.getInstance();
|
||||
helper = new ListHabitsHelper(getContext(), loader);
|
||||
|
||||
adapter.setSelectedPositions(selectedPositions);
|
||||
adapter.setOnCheckmarkClickListener(this);
|
||||
adapter.setOnCheckmarkLongClickListener(this);
|
||||
loader.setListener(this);
|
||||
loader.setCheckmarkCount(helper.getButtonCount());
|
||||
|
||||
setAdapter(adapter);
|
||||
setOnItemClickListener(this);
|
||||
setOnItemLongClickListener(this);
|
||||
setDropListener(this);
|
||||
setDragListener(this);
|
||||
setFloatViewManager(new HabitsDragSortController());
|
||||
setDragEnabled(false);
|
||||
setLongClickable(true);
|
||||
}
|
||||
|
||||
public HabitListLoader getLoader()
|
||||
{
|
||||
return loader;
|
||||
}
|
||||
|
||||
public List<Integer> getSelectedPositions()
|
||||
{
|
||||
return selectedPositions;
|
||||
}
|
||||
|
||||
public void setListener(@Nullable Listener l)
|
||||
{
|
||||
this.listener = l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drop(int from, int to)
|
||||
{
|
||||
if(from == to) return;
|
||||
cancelSelection();
|
||||
|
||||
loader.reorder(from, to);
|
||||
adapter.notifyDataSetChanged();
|
||||
loader.updateAllHabits(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
if (v.getId() != R.id.tvCheck) return;
|
||||
|
||||
if (prefs.isShortToggleEnabled()) toggleCheckmark(v);
|
||||
else if(listener != null) listener.onInvalidToggle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v)
|
||||
{
|
||||
lastLongClick = new Date().getTime();
|
||||
if (v.getId() != R.id.tvCheck) return true;
|
||||
if (prefs.isShortToggleEnabled()) return true;
|
||||
toggleCheckmark(v);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void toggleShowArchived()
|
||||
{
|
||||
showArchived = !showArchived;
|
||||
loader.setIncludeArchived(showArchived);
|
||||
loader.updateAllHabits(true);
|
||||
}
|
||||
|
||||
private void toggleCheckmark(View v)
|
||||
{
|
||||
Long id = helper.getHabitIdFromCheckmarkView(v);
|
||||
Habit habit = loader.habits.get(id);
|
||||
if(habit == null) return;
|
||||
|
||||
float x = v.getX() + v.getWidth() / 2.0f + ((View) v.getParent()).getX();
|
||||
float y = v.getY() + v.getHeight() / 2.0f + ((View) v.getParent()).getY();
|
||||
helper.triggerRipple((View) v.getParent().getParent(), x, y);
|
||||
|
||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
helper.toggleCheckmarkView(v, habit);
|
||||
|
||||
long timestamp = helper.getTimestampFromCheckmarkView(v);
|
||||
|
||||
if(listener != null) listener.onToggleCheckmark(habit, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
if (new Date().getTime() - lastLongClick < 1000) return;
|
||||
|
||||
if(selectedPositions.isEmpty())
|
||||
{
|
||||
Habit habit = loader.habitsList.get(position);
|
||||
if(listener != null) listener.onHabitClick(habit);
|
||||
}
|
||||
else
|
||||
{
|
||||
toggleItemSelected(position);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleItemSelected(int position)
|
||||
{
|
||||
int k = selectedPositions.indexOf(position);
|
||||
if(k < 0) selectedPositions.add(position);
|
||||
else selectedPositions.remove(k);
|
||||
|
||||
if(listener != null)
|
||||
{
|
||||
if (selectedPositions.isEmpty()) listener.onHabitSelectionFinish();
|
||||
else listener.onHabitSelectionChange();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
selectHabit(position);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void selectHabit(int position)
|
||||
{
|
||||
if(!selectedPositions.contains(position)) selectedPositions.add(position);
|
||||
adapter.notifyDataSetChanged();
|
||||
|
||||
if(listener != null)
|
||||
{
|
||||
if (selectedPositions.size() == 1) listener.onHabitSelectionStart();
|
||||
else listener.onHabitSelectionChange();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drag(int from, int to)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDrag(int position)
|
||||
{
|
||||
selectHabit(position);
|
||||
}
|
||||
|
||||
public boolean getShowArchived()
|
||||
{
|
||||
return showArchived;
|
||||
}
|
||||
|
||||
public void cancelSelection()
|
||||
{
|
||||
selectedPositions.clear();
|
||||
adapter.notifyDataSetChanged();
|
||||
setDragEnabled(true);
|
||||
if(listener != null) listener.onHabitSelectionFinish();
|
||||
}
|
||||
|
||||
public void refreshData(Long refreshKey)
|
||||
{
|
||||
if (refreshKey == null) loader.updateAllHabits(true);
|
||||
else loader.updateHabit(refreshKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished()
|
||||
{
|
||||
adapter.notifyDataSetChanged();
|
||||
if(listener != null) listener.onDatasetChanged();
|
||||
}
|
||||
|
||||
private class HabitsDragSortController extends DragSortController
|
||||
{
|
||||
public HabitsDragSortController()
|
||||
{
|
||||
super(HabitListView.this);
|
||||
setRemoveEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateFloatView(int position)
|
||||
{
|
||||
return adapter.getView(position, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyFloatView(View floatView)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public interface Listener
|
||||
{
|
||||
void onToggleCheckmark(Habit habit, long timestamp);
|
||||
|
||||
void onHabitClick(Habit habit);
|
||||
|
||||
void onHabitSelectionStart();
|
||||
|
||||
void onHabitSelectionFinish();
|
||||
|
||||
void onHabitSelectionChange();
|
||||
|
||||
void onInvalidToggle();
|
||||
|
||||
void onDatasetChanged();
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.os.Bundle;
|
||||
|
||||
import org.isoron.uhabits.ui.BaseActivity;
|
||||
import org.isoron.uhabits.ui.BaseSystem;
|
||||
|
||||
/**
|
||||
* Activity that allows the user to see and modify the list of habits.
|
||||
*/
|
||||
public class ListHabitsActivity extends BaseActivity
|
||||
{
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
BaseSystem system = new BaseSystem(this);
|
||||
ListHabitsScreen screen = new ListHabitsScreen(this);
|
||||
ListHabitsController controller =
|
||||
new ListHabitsController(screen, system);
|
||||
|
||||
screen.setController(controller);
|
||||
|
||||
setScreen(screen);
|
||||
controller.onStartup();
|
||||
}
|
||||
}
|
@ -1,236 +0,0 @@
|
||||
/*
|
||||
* 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.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.commands.CommandRunner;
|
||||
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.ui.BaseActivity;
|
||||
import org.isoron.uhabits.ui.HintManager;
|
||||
import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment;
|
||||
import org.isoron.uhabits.ui.habits.edit.CreateHabitDialogFragment;
|
||||
import org.isoron.uhabits.utils.InterfaceUtils;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class ListHabitsFragment extends Fragment
|
||||
implements HabitListSelectionCallback.Listener, ListHabitsController.Screen
|
||||
{
|
||||
private ActionMode actionMode;
|
||||
private HintManager hintManager;
|
||||
private ListHabitsHelper helper;
|
||||
private Listener habitClickListener;
|
||||
private BaseActivity activity;
|
||||
|
||||
@BindView(R.id.listView) HabitListView listView;
|
||||
@BindView(R.id.llButtonsHeader) LinearLayout llButtonsHeader;
|
||||
@BindView(R.id.progressBar) ProgressBar progressBar;
|
||||
@BindView(R.id.llEmpty) View llEmpty;
|
||||
@BindView(R.id.llHint) View llHint;
|
||||
@BindView(R.id.tvStarEmpty) TextView tvStarEmpty;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState)
|
||||
{
|
||||
View view = inflater.inflate(R.layout.list_habits_fragment, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
|
||||
helper = new ListHabitsHelper(activity, listView.getLoader());
|
||||
hintManager = new HintManager(activity, llHint);
|
||||
tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(activity));
|
||||
listView.setListener(new HabitListViewListener());
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void onAttach(Activity activity)
|
||||
{
|
||||
super.onAttach(activity);
|
||||
this.activity = (BaseActivity) activity;
|
||||
habitClickListener = (Listener) activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
listView.getLoader().onResume();
|
||||
listView.refreshData(null);
|
||||
helper.updateEmptyMessage(llEmpty);
|
||||
helper.updateHeader(llButtonsHeader);
|
||||
hintManager.showHintIfAppropriate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
listView.getLoader().onPause();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
|
||||
{
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.list_habits_fragment, menu);
|
||||
MenuItem showArchivedItem = menu.findItem(R.id.action_show_archived);
|
||||
showArchivedItem.setChecked(listView.getShowArchived());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.action_add:
|
||||
showCreateHabitScreen();
|
||||
return true;
|
||||
|
||||
case R.id.action_show_archived:
|
||||
toggleShowArchived();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleShowArchived()
|
||||
{
|
||||
listView.toggleShowArchived();
|
||||
activity.invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
private void showCreateHabitScreen()
|
||||
{
|
||||
BaseDialogFragment frag = new CreateHabitDialogFragment();
|
||||
frag.show(getFragmentManager(), "editHabit");
|
||||
}
|
||||
|
||||
private void startActionMode()
|
||||
{
|
||||
HabitListSelectionCallback callback =
|
||||
new HabitListSelectionCallback(activity, listView.getLoader());
|
||||
callback.setSelectedPositions(listView.getSelectedPositions());
|
||||
callback.setListener(this);
|
||||
actionMode = activity.startSupportActionMode(callback);
|
||||
}
|
||||
|
||||
private void finishActionMode()
|
||||
{
|
||||
if(actionMode != null) actionMode.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActionModeDestroyed(ActionMode mode)
|
||||
{
|
||||
actionMode = null;
|
||||
listView.cancelSelection();
|
||||
}
|
||||
|
||||
@OnClick(R.id.llHint)
|
||||
public void onClickHint()
|
||||
{
|
||||
hintManager.dismissHint();
|
||||
}
|
||||
|
||||
public ProgressBar getProgressBar()
|
||||
{
|
||||
return progressBar;
|
||||
}
|
||||
|
||||
public void refresh(Long refreshKey)
|
||||
{
|
||||
listView.refreshData(refreshKey);
|
||||
}
|
||||
|
||||
public interface Listener
|
||||
{
|
||||
void onHabitClick(Habit habit);
|
||||
}
|
||||
|
||||
private class HabitListViewListener implements HabitListView.Listener
|
||||
{
|
||||
@Override
|
||||
public void onToggleCheckmark(Habit habit, long timestamp)
|
||||
{
|
||||
CommandRunner.getInstance().execute(new ToggleRepetitionCommand(habit, timestamp),
|
||||
habit.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHabitClick(Habit habit)
|
||||
{
|
||||
habitClickListener.onHabitClick(habit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHabitSelectionStart()
|
||||
{
|
||||
if(actionMode == null) startActionMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHabitSelectionFinish()
|
||||
{
|
||||
finishActionMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHabitSelectionChange()
|
||||
{
|
||||
if(actionMode != null) actionMode.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInvalidToggle()
|
||||
{
|
||||
activity.showMessage(R.string.long_press_to_toggle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDatasetChanged()
|
||||
{
|
||||
helper.updateEmptyMessage(llEmpty);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,307 +0,0 @@
|
||||
/*
|
||||
* 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.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.utils.ColorUtils;
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
import org.isoron.uhabits.utils.InterfaceUtils;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
import org.isoron.uhabits.views.RingView;
|
||||
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
public class ListHabitsHelper
|
||||
{
|
||||
private static final int CHECKMARK_LEFT_TO_RIGHT = 0;
|
||||
private static final int CHECKMARK_RIGHT_TO_LEFT = 1;
|
||||
|
||||
private final int lowContrastColor;
|
||||
private final int mediumContrastColor;
|
||||
|
||||
private final Context context;
|
||||
private final HabitListLoader loader;
|
||||
|
||||
public ListHabitsHelper(Context context, HabitListLoader loader)
|
||||
{
|
||||
this.context = context;
|
||||
this.loader = loader;
|
||||
|
||||
lowContrastColor = InterfaceUtils.getStyledColor(context, R.attr.lowContrastTextColor);
|
||||
mediumContrastColor = InterfaceUtils.getStyledColor(context, R.attr.mediumContrastTextColor);
|
||||
}
|
||||
|
||||
public int getButtonCount()
|
||||
{
|
||||
float screenWidth = InterfaceUtils.getScreenWidth(context);
|
||||
float labelWidth = context.getResources().getDimension(R.dimen.habitNameWidth);
|
||||
float buttonWidth = context.getResources().getDimension(R.dimen.checkmarkWidth);
|
||||
return Math.max(0, (int) ((screenWidth - labelWidth) / buttonWidth));
|
||||
}
|
||||
|
||||
public int getHabitNameWidth()
|
||||
{
|
||||
float screenWidth = InterfaceUtils.getScreenWidth(context);
|
||||
float buttonWidth = context.getResources().getDimension(R.dimen.checkmarkWidth);
|
||||
float padding = InterfaceUtils.dpToPixels(context, 15);
|
||||
return (int) (screenWidth - padding - getButtonCount() * buttonWidth);
|
||||
}
|
||||
|
||||
public void updateCheckmarkButtons(Habit habit, LinearLayout llButtons)
|
||||
{
|
||||
int activeColor = getActiveColor(habit);
|
||||
int m = llButtons.getChildCount();
|
||||
Long habitId = habit.getId();
|
||||
|
||||
int isChecked[] = loader.checkmarks.get(habitId);
|
||||
|
||||
for (int i = 0; i < m; i++)
|
||||
{
|
||||
int position = i;
|
||||
|
||||
if(getCheckmarkOrder() == CHECKMARK_RIGHT_TO_LEFT)
|
||||
position = m - i - 1;
|
||||
|
||||
TextView tvCheck = (TextView) llButtons.getChildAt(position);
|
||||
tvCheck.setTag(R.string.habit_key, habitId);
|
||||
tvCheck.setTag(R.string.offset_key, i);
|
||||
if(isChecked.length > i)
|
||||
updateCheckmark(activeColor, tvCheck, isChecked[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public int getActiveColor(Habit habit)
|
||||
{
|
||||
int activeColor = ColorUtils.getColor(context, habit.color);
|
||||
if(habit.isArchived()) activeColor = mediumContrastColor;
|
||||
|
||||
return activeColor;
|
||||
}
|
||||
|
||||
public void initializeLabelAndIcon(View itemView)
|
||||
{
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getHabitNameWidth(),
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT, 1);
|
||||
itemView.findViewById(R.id.label).setLayoutParams(params);
|
||||
}
|
||||
|
||||
public void updateNameAndIcon(Habit habit, RingView ring, TextView tvName)
|
||||
{
|
||||
int activeColor = getActiveColor(habit);
|
||||
|
||||
tvName.setText(habit.name);
|
||||
tvName.setTextColor(activeColor);
|
||||
|
||||
int score = loader.scores.get(habit.getId());
|
||||
float percentage = (float) score / Score.MAX_VALUE;
|
||||
|
||||
ring.setColor(activeColor);
|
||||
ring.setPercentage(percentage);
|
||||
ring.setPrecision(1.0f / 16);
|
||||
}
|
||||
|
||||
public void updateCheckmark(int activeColor, TextView tvCheck, int check)
|
||||
{
|
||||
switch (check)
|
||||
{
|
||||
case 2:
|
||||
tvCheck.setText(R.string.fa_check);
|
||||
tvCheck.setTextColor(activeColor);
|
||||
tvCheck.setTag(R.string.toggle_key, 2);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
tvCheck.setText(R.string.fa_check);
|
||||
tvCheck.setTextColor(lowContrastColor);
|
||||
tvCheck.setTag(R.string.toggle_key, 1);
|
||||
break;
|
||||
|
||||
case 0:
|
||||
tvCheck.setText(R.string.fa_times);
|
||||
tvCheck.setTextColor(lowContrastColor);
|
||||
tvCheck.setTag(R.string.toggle_key, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public View inflateHabitCard(LayoutInflater inflater,
|
||||
View.OnLongClickListener onCheckmarkLongClickListener,
|
||||
View.OnClickListener onCheckmarkClickListener)
|
||||
{
|
||||
View view = inflater.inflate(R.layout.list_habits_item, null);
|
||||
initializeLabelAndIcon(view);
|
||||
inflateCheckmarkButtons(view, onCheckmarkLongClickListener, onCheckmarkClickListener,
|
||||
inflater);
|
||||
return view;
|
||||
}
|
||||
|
||||
public void updateHabitCard(View view, Habit habit, boolean selected)
|
||||
{
|
||||
RingView scoreRing = ((RingView) view.findViewById(R.id.scoreRing));
|
||||
TextView tvName = (TextView) view.findViewById(R.id.label);
|
||||
LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner);
|
||||
LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons);
|
||||
|
||||
llInner.setTag(R.string.habit_key, habit.getId());
|
||||
llInner.setOnTouchListener(new HotspotTouchListener());
|
||||
|
||||
updateNameAndIcon(habit, scoreRing, tvName);
|
||||
updateCheckmarkButtons(habit, llButtons);
|
||||
updateHabitCardBackground(llInner, selected);
|
||||
}
|
||||
|
||||
|
||||
public void updateHabitCardBackground(View view, boolean isSelected)
|
||||
{
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
{
|
||||
if (isSelected)
|
||||
view.setBackgroundResource(R.drawable.selected_box);
|
||||
else
|
||||
view.setBackgroundResource(R.drawable.ripple);
|
||||
}
|
||||
else
|
||||
{
|
||||
Drawable background;
|
||||
|
||||
if (isSelected)
|
||||
background = InterfaceUtils.getStyledDrawable(context, R.attr.selectedBackground);
|
||||
else
|
||||
background = InterfaceUtils.getStyledDrawable(context, R.attr.cardBackground);
|
||||
|
||||
view.setBackgroundDrawable(background);
|
||||
}
|
||||
}
|
||||
|
||||
public void inflateCheckmarkButtons(View view, View.OnLongClickListener onLongClickListener,
|
||||
View.OnClickListener onClickListener, LayoutInflater inflater)
|
||||
{
|
||||
for (int i = 0; i < getButtonCount(); i++)
|
||||
{
|
||||
View check = inflater.inflate(R.layout.list_habits_item_check, null);
|
||||
TextView btCheck = (TextView) check.findViewById(R.id.tvCheck);
|
||||
btCheck.setTypeface(InterfaceUtils.getFontAwesome(context));
|
||||
btCheck.setOnLongClickListener(onLongClickListener);
|
||||
btCheck.setOnClickListener(onClickListener);
|
||||
btCheck.setHapticFeedbackEnabled(false);
|
||||
((LinearLayout) view.findViewById(R.id.llButtons)).addView(check);
|
||||
}
|
||||
|
||||
view.setTag(R.id.timestamp_key, DateUtils.getStartOfToday());
|
||||
}
|
||||
|
||||
public void updateHeader(ViewGroup header)
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
GregorianCalendar day = DateUtils.getStartOfTodayCalendar();
|
||||
header.removeAllViews();
|
||||
|
||||
for (int i = 0; i < getButtonCount(); i++)
|
||||
{
|
||||
int position = 0;
|
||||
|
||||
if(getCheckmarkOrder() == CHECKMARK_LEFT_TO_RIGHT)
|
||||
position = i;
|
||||
|
||||
View tvDay = inflater.inflate(R.layout.list_habits_header_check, null);
|
||||
TextView btCheck = (TextView) tvDay.findViewById(R.id.tvCheck);
|
||||
btCheck.setText(DateUtils.formatHeaderDate(day));
|
||||
header.addView(tvDay, position);
|
||||
day.add(GregorianCalendar.DAY_OF_MONTH, -1);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateEmptyMessage(View view)
|
||||
{
|
||||
if (loader.getLastLoadTimestamp() == null) view.setVisibility(View.GONE);
|
||||
else view.setVisibility(loader.habits.size() > 0 ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
public void toggleCheckmarkView(View v, Habit habit)
|
||||
{
|
||||
int androidColor = ColorUtils.getColor(context, habit.color);
|
||||
|
||||
if (v.getTag(R.string.toggle_key).equals(2))
|
||||
updateCheckmark(androidColor, (TextView) v, 0);
|
||||
else
|
||||
updateCheckmark(androidColor, (TextView) v, 2);
|
||||
}
|
||||
|
||||
public Long getHabitIdFromCheckmarkView(View v)
|
||||
{
|
||||
return (Long) v.getTag(R.string.habit_key);
|
||||
}
|
||||
|
||||
public long getTimestampFromCheckmarkView(View v)
|
||||
{
|
||||
Integer offset = (Integer) v.getTag(R.string.offset_key);
|
||||
return DateUtils.getStartOfDay(DateUtils.getLocalTime() -
|
||||
offset * DateUtils.millisecondsInOneDay);
|
||||
}
|
||||
|
||||
public void triggerRipple(View v, final float x, final float y)
|
||||
{
|
||||
final Drawable background = v.getBackground();
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
background.setHotspot(x, y);
|
||||
|
||||
background.setState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled});
|
||||
|
||||
new Handler().postDelayed(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
background.setState(new int[]{});
|
||||
}
|
||||
}, 25);
|
||||
}
|
||||
|
||||
private static class HotspotTouchListener implements View.OnTouchListener
|
||||
{
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event)
|
||||
{
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
v.getBackground().setHotspot(event.getX(), event.getY());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int getCheckmarkOrder()
|
||||
{
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean reverse = prefs.getBoolean("pref_checkmark_reverse_order", false);
|
||||
return reverse ? CHECKMARK_RIGHT_TO_LEFT : CHECKMARK_LEFT_TO_RIGHT;
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.support.annotation.NonNull;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.ui.BaseActivity;
|
||||
import org.isoron.uhabits.ui.BaseMenu;
|
||||
import org.isoron.uhabits.utils.InterfaceUtils;
|
||||
|
||||
public class ListHabitsMenu extends BaseMenu
|
||||
{
|
||||
@NonNull
|
||||
private final ListHabitsScreen screen;
|
||||
|
||||
private boolean showArchived;
|
||||
|
||||
public ListHabitsMenu(@NonNull BaseActivity activity,
|
||||
@NonNull ListHabitsScreen screen)
|
||||
{
|
||||
super(activity);
|
||||
this.screen = screen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@NonNull Menu menu)
|
||||
{
|
||||
MenuItem nightModeItem = menu.findItem(R.id.action_night_mode);
|
||||
nightModeItem.setChecked(InterfaceUtils.isNightMode());
|
||||
|
||||
MenuItem showArchivedItem = menu.findItem(R.id.action_show_archived);
|
||||
showArchivedItem.setChecked(showArchived);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemSelected(@NonNull MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.action_night_mode:
|
||||
screen.toggleNightMode();
|
||||
return true;
|
||||
|
||||
case R.id.action_add:
|
||||
screen.showCreateHabitScreen();
|
||||
return true;
|
||||
|
||||
case R.id.action_faq:
|
||||
screen.showFAQScreen();
|
||||
return true;
|
||||
|
||||
case R.id.action_about:
|
||||
screen.showAboutScreen();
|
||||
return true;
|
||||
|
||||
case R.id.action_settings:
|
||||
screen.showSettingsScreen();
|
||||
return true;
|
||||
|
||||
case R.id.action_show_archived:
|
||||
showArchived = !showArchived;
|
||||
screen.getRootView().setShowArchived(showArchived);
|
||||
invalidate();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMenuResourceId()
|
||||
{
|
||||
return R.menu.main_activity;
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.ModelObservable;
|
||||
import org.isoron.uhabits.ui.BaseRootView;
|
||||
import org.isoron.uhabits.ui.habits.list.controllers.HabitCardListController;
|
||||
import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter;
|
||||
import org.isoron.uhabits.ui.habits.list.model.HintList;
|
||||
import org.isoron.uhabits.ui.habits.list.views.HabitCardListView;
|
||||
import org.isoron.uhabits.ui.habits.list.views.HintView;
|
||||
import org.isoron.uhabits.utils.InterfaceUtils;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class ListHabitsRootView extends BaseRootView
|
||||
implements ModelObservable.Listener
|
||||
{
|
||||
@BindView(R.id.listView)
|
||||
HabitCardListView listView;
|
||||
|
||||
@BindView(R.id.llEmpty)
|
||||
ViewGroup llEmpty;
|
||||
|
||||
@BindView(R.id.tvStarEmpty)
|
||||
TextView tvStarEmpty;
|
||||
|
||||
@BindView(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
|
||||
@BindView(R.id.progressBar)
|
||||
ProgressBar progressBar;
|
||||
|
||||
@BindView(R.id.hintView)
|
||||
HintView hintView;
|
||||
|
||||
@Nullable
|
||||
private HabitCardListAdapter listAdapter;
|
||||
|
||||
public ListHabitsRootView(@NonNull Context context)
|
||||
{
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ProgressBar getProgressBar()
|
||||
{
|
||||
return progressBar;
|
||||
}
|
||||
|
||||
public boolean getShowArchived()
|
||||
{
|
||||
if(listAdapter == null) return false;
|
||||
return listAdapter.getIncludeArchived();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Toolbar getToolbar()
|
||||
{
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getToolbarColor()
|
||||
{
|
||||
return InterfaceUtils.getStyledColor(getContext(), R.attr.colorPrimary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModelChange()
|
||||
{
|
||||
updateEmptyView();
|
||||
}
|
||||
|
||||
public void setShowArchived(boolean showArchived)
|
||||
{
|
||||
if(listAdapter == null) return;
|
||||
listAdapter.setShowArchived(showArchived);
|
||||
}
|
||||
|
||||
private void updateEmptyView()
|
||||
{
|
||||
if (listAdapter == null) return;
|
||||
llEmpty.setVisibility(
|
||||
listAdapter.getCount() > 0 ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
public void setController(@Nullable ListHabitsController controller,
|
||||
@Nullable ListHabitsSelectionMenu menu)
|
||||
{
|
||||
listView.setController(null);
|
||||
if (controller == null || listAdapter == null) return;
|
||||
|
||||
HabitCardListController listController =
|
||||
new HabitCardListController(listAdapter, listView);
|
||||
listController.setHabitListener(controller);
|
||||
listController.setSelectionListener(menu);
|
||||
listView.setController(listController);
|
||||
}
|
||||
|
||||
public void setListAdapter(@NonNull HabitCardListAdapter listAdapter)
|
||||
{
|
||||
if (this.listAdapter != null)
|
||||
listAdapter.getObservable().removeListener(this);
|
||||
|
||||
this.listAdapter = listAdapter;
|
||||
listView.setAdapter(listAdapter);
|
||||
listAdapter.setListView(listView);
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
addView(inflate(getContext(), R.layout.list_habits, null));
|
||||
ButterKnife.bind(this);
|
||||
|
||||
tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(getContext()));
|
||||
initToolbar();
|
||||
|
||||
String hints[] =
|
||||
getContext().getResources().getStringArray(R.array.hints);
|
||||
HintList hintList = new HintList(hints);
|
||||
hintView.setHints(hintList);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow()
|
||||
{
|
||||
super.onAttachedToWindow();
|
||||
|
||||
updateEmptyView();
|
||||
|
||||
if (listAdapter != null) listAdapter.getObservable().addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow()
|
||||
{
|
||||
if (listAdapter != null)
|
||||
listAdapter.getObservable().removeListener(this);
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
}
|
@ -0,0 +1,236 @@
|
||||
/*
|
||||
* 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.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import com.android.colorpicker.ColorPickerDialog;
|
||||
|
||||
import org.isoron.uhabits.HabitsApplication;
|
||||
import org.isoron.uhabits.MainActivity;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.ui.BaseActivity;
|
||||
import org.isoron.uhabits.ui.BaseScreen;
|
||||
import org.isoron.uhabits.ui.about.AboutActivity;
|
||||
import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment;
|
||||
import org.isoron.uhabits.ui.habits.edit.CreateHabitDialogFragment;
|
||||
import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment;
|
||||
import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter;
|
||||
import org.isoron.uhabits.ui.habits.show.ShowHabitActivity;
|
||||
import org.isoron.uhabits.ui.intro.IntroActivity;
|
||||
import org.isoron.uhabits.ui.settings.FilePickerDialog;
|
||||
import org.isoron.uhabits.ui.settings.SettingsActivity;
|
||||
import org.isoron.uhabits.utils.ColorUtils;
|
||||
import org.isoron.uhabits.utils.FileUtils;
|
||||
import org.isoron.uhabits.utils.InterfaceUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ListHabitsScreen extends BaseScreen
|
||||
{
|
||||
@Nullable
|
||||
ListHabitsController controller;
|
||||
|
||||
@NonNull
|
||||
private final ListHabitsRootView rootView;
|
||||
|
||||
@NonNull
|
||||
private final ListHabitsSelectionMenu selectionMenu;
|
||||
|
||||
public ListHabitsScreen(@NonNull BaseActivity activity)
|
||||
{
|
||||
super(activity);
|
||||
rootView = new ListHabitsRootView(activity);
|
||||
setRootView(rootView);
|
||||
|
||||
ListHabitsMenu menu = new ListHabitsMenu(activity, this);
|
||||
selectionMenu = new ListHabitsSelectionMenu(this);
|
||||
setMenu(menu);
|
||||
setSelectionMenu(selectionMenu);
|
||||
|
||||
HabitCardListAdapter adapter = new HabitCardListAdapter();
|
||||
rootView.setListAdapter(adapter);
|
||||
selectionMenu.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
if (controller == null) return;
|
||||
|
||||
switch (resultCode)
|
||||
{
|
||||
case HabitsApplication.RESULT_IMPORT_DATA:
|
||||
showImportScreen();
|
||||
break;
|
||||
|
||||
case HabitsApplication.RESULT_EXPORT_CSV:
|
||||
controller.onExportCSV();
|
||||
break;
|
||||
|
||||
case HabitsApplication.RESULT_EXPORT_DB:
|
||||
controller.onExportDB();
|
||||
break;
|
||||
|
||||
case HabitsApplication.RESULT_BUG_REPORT:
|
||||
controller.onSendBugReport();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setController(@Nullable ListHabitsController controller)
|
||||
{
|
||||
this.controller = controller;
|
||||
rootView.setController(controller, selectionMenu);
|
||||
}
|
||||
|
||||
public void showAboutScreen()
|
||||
{
|
||||
Intent intent = new Intent(activity, AboutActivity.class);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
|
||||
public void showColorPicker(Habit habit, OnColorSelectedListener callback)
|
||||
{
|
||||
int color = ColorUtils.getColor(activity, habit.color);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
public void showCreateHabitScreen()
|
||||
{
|
||||
showDialog(new CreateHabitDialogFragment(), "editHabit");
|
||||
}
|
||||
|
||||
public void showDeleteConfirmationScreen(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();
|
||||
}
|
||||
|
||||
public void showEditHabitScreen(Habit habit)
|
||||
{
|
||||
BaseDialogFragment frag =
|
||||
EditHabitDialogFragment.newInstance(habit.getId());
|
||||
frag.show(activity.getSupportFragmentManager(), "editHabit");
|
||||
}
|
||||
|
||||
public void showFAQScreen()
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(activity.getString(R.string.helpURL)));
|
||||
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()));
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
|
||||
public void showImportScreen()
|
||||
{
|
||||
if (controller == null) return;
|
||||
|
||||
File dir = FileUtils.getFilesDir(null);
|
||||
if (dir == null)
|
||||
{
|
||||
showMessage(R.string.could_not_import);
|
||||
return;
|
||||
}
|
||||
|
||||
FilePickerDialog picker = new FilePickerDialog(activity, dir);
|
||||
picker.setListener(file -> controller.onImportData(file));
|
||||
picker.show();
|
||||
}
|
||||
|
||||
public void showIntroScreen()
|
||||
{
|
||||
Intent intent = new Intent(activity, IntroActivity.class);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
|
||||
public void showSettingsScreen()
|
||||
{
|
||||
Intent intent = new Intent(activity, SettingsActivity.class);
|
||||
activity.startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
public void toggleNightMode()
|
||||
{
|
||||
if (InterfaceUtils.isNightMode())
|
||||
InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_LIGHT);
|
||||
else InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_DARK);
|
||||
|
||||
refreshTheme();
|
||||
}
|
||||
|
||||
private void refreshTheme()
|
||||
{
|
||||
new Handler().postDelayed(() -> {
|
||||
Intent intent = new Intent(activity, MainActivity.class);
|
||||
|
||||
activity.finish();
|
||||
activity.overridePendingTransition(android.R.anim.fade_in,
|
||||
android.R.anim.fade_out);
|
||||
activity.startActivity(intent);
|
||||
|
||||
}, 500); // HACK: Let the menu disappear first
|
||||
}
|
||||
|
||||
interface Callback
|
||||
{
|
||||
void run();
|
||||
}
|
||||
|
||||
public interface OnColorSelectedListener
|
||||
{
|
||||
void onColorSelected(int color);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ListHabitsRootView getRootView()
|
||||
{
|
||||
return rootView;
|
||||
}
|
||||
}
|
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.isoron.uhabits.HabitsApplication;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.commands.ArchiveHabitsCommand;
|
||||
import org.isoron.uhabits.commands.ChangeHabitColorCommand;
|
||||
import org.isoron.uhabits.commands.CommandRunner;
|
||||
import org.isoron.uhabits.commands.DeleteHabitsCommand;
|
||||
import org.isoron.uhabits.commands.UnarchiveHabitsCommand;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.ui.BaseSelectionMenu;
|
||||
import org.isoron.uhabits.ui.habits.list.controllers.HabitCardListController;
|
||||
import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class ListHabitsSelectionMenu extends BaseSelectionMenu
|
||||
implements HabitCardListController.SelectionListener
|
||||
{
|
||||
@NonNull
|
||||
private final ListHabitsScreen screen;
|
||||
|
||||
@Inject
|
||||
CommandRunner commandRunner;
|
||||
|
||||
@Nullable
|
||||
private HabitCardListAdapter adapter;
|
||||
|
||||
public ListHabitsSelectionMenu(@NonNull ListHabitsScreen screen)
|
||||
{
|
||||
this.screen = screen;
|
||||
HabitsApplication.getComponent().inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
if (adapter != null) adapter.clearSelection();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemClicked(@NonNull MenuItem item)
|
||||
{
|
||||
if (adapter == null) return false;
|
||||
|
||||
List<Habit> selected = adapter.getSelected();
|
||||
if (selected.isEmpty()) return false;
|
||||
|
||||
Habit firstHabit = selected.get(0);
|
||||
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.action_edit_habit:
|
||||
edit(firstHabit);
|
||||
finish();
|
||||
return true;
|
||||
|
||||
case R.id.action_archive_habit:
|
||||
archive(selected);
|
||||
finish();
|
||||
return true;
|
||||
|
||||
case R.id.action_unarchive_habit:
|
||||
unarchive(selected);
|
||||
finish();
|
||||
return true;
|
||||
|
||||
case R.id.action_delete:
|
||||
delete(selected);
|
||||
return true;
|
||||
|
||||
case R.id.action_color:
|
||||
showColorPicker(selected, firstHabit);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepare(@NonNull Menu menu)
|
||||
{
|
||||
if (adapter == null) return false;
|
||||
List<Habit> selected = adapter.getSelected();
|
||||
|
||||
boolean showEdit = (selected.size() == 1);
|
||||
boolean showArchive = true;
|
||||
boolean showUnarchive = true;
|
||||
for (Habit h : selected)
|
||||
{
|
||||
if (h.isArchived()) showArchive = false;
|
||||
else showUnarchive = false;
|
||||
}
|
||||
|
||||
MenuItem itemEdit = menu.findItem(R.id.action_edit_habit);
|
||||
MenuItem itemColor = menu.findItem(R.id.action_color);
|
||||
MenuItem itemArchive = menu.findItem(R.id.action_archive_habit);
|
||||
MenuItem itemUnarchive = menu.findItem(R.id.action_unarchive_habit);
|
||||
|
||||
itemColor.setVisible(true);
|
||||
itemEdit.setVisible(showEdit);
|
||||
itemArchive.setVisible(showArchive);
|
||||
itemUnarchive.setVisible(showUnarchive);
|
||||
|
||||
setTitle(Integer.toString(selected.size()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionChange()
|
||||
{
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionFinish()
|
||||
{
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionStart()
|
||||
{
|
||||
screen.startSelection();
|
||||
}
|
||||
|
||||
public void setAdapter(@Nullable HabitCardListAdapter adapter)
|
||||
{
|
||||
if (adapter == null) return;
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
private void archive(@NonNull List<Habit> selected)
|
||||
{
|
||||
commandRunner.execute(new ArchiveHabitsCommand(selected), null);
|
||||
}
|
||||
|
||||
private void delete(@NonNull List<Habit> selected)
|
||||
{
|
||||
screen.showDeleteConfirmationScreen(() -> {
|
||||
commandRunner.execute(new DeleteHabitsCommand(selected), null);
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
private void edit(@NonNull Habit firstHabit)
|
||||
{
|
||||
screen.showEditHabitScreen(firstHabit);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getResourceId()
|
||||
{
|
||||
return R.menu.list_habits_selection;
|
||||
}
|
||||
|
||||
private void showColorPicker(@NonNull List<Habit> selected,
|
||||
@NonNull Habit firstHabit)
|
||||
{
|
||||
screen.showColorPicker(firstHabit, color -> {
|
||||
commandRunner.execute(new ChangeHabitColorCommand(selected, color),
|
||||
null);
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
private void unarchive(@NonNull List<Habit> selected)
|
||||
{
|
||||
commandRunner.execute(new UnarchiveHabitsCommand(selected), null);
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.controllers;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.isoron.uhabits.HabitsApplication;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.ui.habits.list.views.CheckmarkButtonView;
|
||||
import org.isoron.uhabits.utils.Preferences;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class CheckmarkButtonController
|
||||
{
|
||||
@Nullable
|
||||
private CheckmarkButtonView view;
|
||||
|
||||
@Nullable
|
||||
private Listener listener;
|
||||
|
||||
@Inject
|
||||
Preferences prefs;
|
||||
|
||||
@NonNull
|
||||
private Habit habit;
|
||||
|
||||
private long timestamp;
|
||||
|
||||
public CheckmarkButtonController(@NonNull Habit habit, long timestamp)
|
||||
{
|
||||
this.habit = habit;
|
||||
this.timestamp = timestamp;
|
||||
HabitsApplication.getComponent().inject(this);
|
||||
}
|
||||
|
||||
public void onClick()
|
||||
{
|
||||
if (prefs.isShortToggleEnabled()) performToggle();
|
||||
else performInvalidToggle();
|
||||
}
|
||||
|
||||
public boolean onLongClick()
|
||||
{
|
||||
performToggle();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void performInvalidToggle()
|
||||
{
|
||||
if (listener != null) listener.onInvalidToggle();
|
||||
}
|
||||
|
||||
public void performToggle()
|
||||
{
|
||||
if (view != null) view.toggle();
|
||||
if (listener != null) listener.onToggle(habit, timestamp);
|
||||
}
|
||||
|
||||
public void setListener(@Nullable Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void setView(@Nullable CheckmarkButtonView view)
|
||||
{
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
public interface Listener
|
||||
{
|
||||
/**
|
||||
* Called when the user's attempt to perform a toggle is rejected.
|
||||
*/
|
||||
void onInvalidToggle();
|
||||
|
||||
|
||||
void onToggle(Habit habit, long timestamp);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.controllers;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.ui.habits.list.views.HabitCardView;
|
||||
|
||||
public class HabitCardController implements HabitCardView.Controller
|
||||
{
|
||||
@Nullable
|
||||
private HabitCardView view;
|
||||
|
||||
@Nullable
|
||||
private Listener listener;
|
||||
|
||||
@Override
|
||||
public void onInvalidToggle()
|
||||
{
|
||||
if (listener != null) listener.onInvalidToggle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToggle(Habit habit, long timestamp)
|
||||
{
|
||||
if (view != null) view.triggerRipple(0, 0);
|
||||
if (listener != null) listener.onToggle(habit, timestamp);
|
||||
}
|
||||
|
||||
public void setListener(@Nullable Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void setView(@Nullable HabitCardView view)
|
||||
{
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
public interface Listener extends CheckmarkButtonController.Listener
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,317 @@
|
||||
/*
|
||||
* 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.controllers;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.mobeta.android.dslv.DragSortListView;
|
||||
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter;
|
||||
import org.isoron.uhabits.ui.habits.list.views.HabitCardListView;
|
||||
|
||||
/**
|
||||
* Controller responsible for receiving and processing the events generated by a
|
||||
* HabitListView. These include selecting and reordering items, toggling
|
||||
* checkmarks and clicking habits.
|
||||
*/
|
||||
public class HabitCardListController implements DragSortListView.DropListener,
|
||||
DragSortListView.DragListener,
|
||||
HabitCardListView.Controller
|
||||
{
|
||||
private final Mode NORMAL_MODE = new NormalMode();
|
||||
|
||||
private final Mode SELECTION_MODE = new SelectionMode();
|
||||
|
||||
@NonNull
|
||||
private final HabitCardListAdapter adapter;
|
||||
|
||||
@NonNull
|
||||
private final HabitCardListView view;
|
||||
|
||||
@Nullable
|
||||
private HabitListener habitListener;
|
||||
|
||||
@Nullable
|
||||
private SelectionListener selectionListener;
|
||||
|
||||
@NonNull
|
||||
private Mode activeMode;
|
||||
|
||||
public HabitCardListController(@NonNull HabitCardListAdapter adapter,
|
||||
@NonNull HabitCardListView view)
|
||||
{
|
||||
this.adapter = adapter;
|
||||
this.view = view;
|
||||
this.activeMode = new NormalMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user is dragging a habit which was originally at position
|
||||
* 'from' and is currently hovering over position 'to'. Note that the user
|
||||
* has not yet finished the dragging operation.
|
||||
*
|
||||
* @param from the original position of the habit
|
||||
* @param to the position where the habit is currently hovering
|
||||
*/
|
||||
@Override
|
||||
public void drag(int from, int to)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user drags a habit and drops it somewhere. Note that the
|
||||
* dragging operation is already complete.
|
||||
*
|
||||
* @param from the original position of the habit
|
||||
* @param to the position where the habit was released
|
||||
*/
|
||||
@Override
|
||||
public void drop(int from, int to)
|
||||
{
|
||||
if (from == to) return;
|
||||
cancelSelection();
|
||||
|
||||
Habit habitFrom = adapter.getItem(from);
|
||||
Habit habitTo = adapter.getItem(to);
|
||||
adapter.reorder(from, to);
|
||||
|
||||
if (habitListener != null)
|
||||
habitListener.onHabitReorder(habitFrom, habitTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user attempts to perform a toggle, but attempt is
|
||||
* rejected.
|
||||
*/
|
||||
@Override
|
||||
public void onInvalidToggle()
|
||||
{
|
||||
if (habitListener != null) habitListener.onInvalidToggle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks at some item.
|
||||
*
|
||||
* @param position the position of the clicked item
|
||||
*/
|
||||
@Override
|
||||
public void onItemClick(int position)
|
||||
{
|
||||
activeMode.onItemClick(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user long clicks at some item.
|
||||
*
|
||||
* @param position the position of the clicked item
|
||||
*/
|
||||
@Override
|
||||
public void onItemLongClick(int position)
|
||||
{
|
||||
activeMode.onItemLongClick(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user wants to toggle a checkmark.
|
||||
*
|
||||
* @param habit the habit of the checkmark
|
||||
* @param timestamp the timestamps of the checkmark
|
||||
*/
|
||||
@Override
|
||||
public void onToggle(Habit habit, long timestamp)
|
||||
{
|
||||
if (habitListener != null) habitListener.onToggle(habit, timestamp);
|
||||
}
|
||||
|
||||
public void setHabitListener(@Nullable HabitListener habitListener)
|
||||
{
|
||||
this.habitListener = habitListener;
|
||||
}
|
||||
|
||||
public void setSelectionListener(@Nullable SelectionListener listener)
|
||||
{
|
||||
this.selectionListener = listener;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the user starts dragging an item.
|
||||
*
|
||||
* @param position the position of the habit dragged
|
||||
*/
|
||||
@Override
|
||||
public void startDrag(int position)
|
||||
{
|
||||
activeMode.startDrag(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks all items as not selected and finishes the selection operation.
|
||||
*/
|
||||
private void cancelSelection()
|
||||
{
|
||||
adapter.clearSelection();
|
||||
view.setDragEnabled(true);
|
||||
activeMode = new NormalMode();
|
||||
|
||||
if (selectionListener != null) selectionListener.onSelectionFinish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects or deselects the item at a given position
|
||||
*
|
||||
* @param position the position of the item to be selected/deselected
|
||||
*/
|
||||
protected void toggleSelection(int position)
|
||||
{
|
||||
adapter.toggleSelection(position);
|
||||
activeMode = adapter.isSelectionEmpty() ? NORMAL_MODE : SELECTION_MODE;
|
||||
}
|
||||
|
||||
public interface HabitListener extends CheckmarkButtonController.Listener
|
||||
{
|
||||
/**
|
||||
* Called when the user clicks a habit.
|
||||
*
|
||||
* @param habit the habit clicked
|
||||
*/
|
||||
void onHabitClick(Habit habit);
|
||||
|
||||
/**
|
||||
* Called when the user wants to change the position of a habit on the
|
||||
* list.
|
||||
*
|
||||
* @param from habit to be moved
|
||||
* @param to habit that currently occupies the desired position
|
||||
*/
|
||||
void onHabitReorder(Habit from, Habit to);
|
||||
}
|
||||
|
||||
/**
|
||||
* A Mode describes the behaviour of the list upon clicking, long clicking
|
||||
* and dragging an item. This depends on whether some items are already
|
||||
* selected or not.
|
||||
*/
|
||||
private interface Mode
|
||||
{
|
||||
void onItemClick(int position);
|
||||
|
||||
boolean onItemLongClick(int position);
|
||||
|
||||
void startDrag(int position);
|
||||
}
|
||||
|
||||
public interface SelectionListener
|
||||
{
|
||||
/**
|
||||
* Called when the user changes the list of selected item. This is only
|
||||
* called if there were previously selected items. If the selection was
|
||||
* previously empty, then onHabitSelectionStart is called instead.
|
||||
*/
|
||||
void onSelectionChange();
|
||||
|
||||
/**
|
||||
* Called when the user deselects all items or cancels the selection.
|
||||
*/
|
||||
void onSelectionFinish();
|
||||
|
||||
/**
|
||||
* Called after the user selects the first item.
|
||||
*/
|
||||
void onSelectionStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mode activated when there are no items selected. Clicks trigger habit
|
||||
* click. Long clicks start selection.
|
||||
*/
|
||||
class NormalMode implements Mode
|
||||
{
|
||||
@Override
|
||||
public void onItemClick(int position)
|
||||
{
|
||||
Habit habit = adapter.getItem(position);
|
||||
if (habitListener != null) habitListener.onHabitClick(habit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemLongClick(int position)
|
||||
{
|
||||
startSelection(position);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDrag(int position)
|
||||
{
|
||||
startSelection(position);
|
||||
}
|
||||
|
||||
protected void startSelection(int position)
|
||||
{
|
||||
toggleSelection(position);
|
||||
activeMode = SELECTION_MODE;
|
||||
if (selectionListener != null) selectionListener.onSelectionStart();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mode activated when some items are already selected.
|
||||
* <p>
|
||||
* Clicks toggle item selection. Long clicks select more items.
|
||||
*/
|
||||
class SelectionMode implements Mode
|
||||
{
|
||||
@Override
|
||||
public void onItemClick(int position)
|
||||
{
|
||||
toggleSelection(position);
|
||||
notifyListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemLongClick(int position)
|
||||
{
|
||||
toggleSelection(position);
|
||||
notifyListener();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDrag(int position)
|
||||
{
|
||||
toggleSelection(position);
|
||||
notifyListener();
|
||||
}
|
||||
|
||||
protected void notifyListener()
|
||||
{
|
||||
if (habitListener == null) return;
|
||||
|
||||
if (activeMode == SELECTION_MODE)
|
||||
selectionListener.onSelectionChange();
|
||||
else
|
||||
selectionListener.onSelectionFinish();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains controllers that are specific for ListHabitsActivity
|
||||
*/
|
||||
package org.isoron.uhabits.ui.habits.list.controllers;
|
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* 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.model;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
|
||||
import org.isoron.uhabits.HabitsApplication;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.ModelObservable;
|
||||
import org.isoron.uhabits.ui.habits.list.views.HabitCardListView;
|
||||
import org.isoron.uhabits.ui.habits.list.views.HabitCardView;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Provides data that backs a {@link HabitCardListView}. The data if fetched and
|
||||
* cached by a {@link HabitCardListCache}. This adapter also holds a list of
|
||||
* items that have been selected.
|
||||
*/
|
||||
public class HabitCardListAdapter extends BaseAdapter
|
||||
implements HabitCardListCache.Listener
|
||||
{
|
||||
@NonNull
|
||||
private ModelObservable observable;
|
||||
|
||||
@Inject
|
||||
@NonNull
|
||||
HabitCardListCache cache;
|
||||
|
||||
@Nullable
|
||||
private HabitCardListView listView;
|
||||
|
||||
@NonNull
|
||||
private final LinkedList<Habit> selected;
|
||||
|
||||
public HabitCardListAdapter()
|
||||
{
|
||||
this.selected = new LinkedList<>();
|
||||
this.observable = new ModelObservable();
|
||||
|
||||
HabitsApplication.getComponent().inject(this);
|
||||
|
||||
cache.setListener(this);
|
||||
cache.setCheckmarkCount(5); // TODO: make this dynamic somehow
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all items as not selected.
|
||||
*/
|
||||
public void clearSelection()
|
||||
{
|
||||
selected.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount()
|
||||
{
|
||||
return cache.getHabitCount();
|
||||
}
|
||||
|
||||
public boolean getIncludeArchived()
|
||||
{
|
||||
return cache.getIncludeArchived();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item that occupies a certain position on the list
|
||||
*
|
||||
* @param position position of the item
|
||||
* @return the item at given position
|
||||
* @throws IndexOutOfBoundsException if position is not valid
|
||||
*/
|
||||
@Override
|
||||
@NonNull
|
||||
public Habit getItem(int position)
|
||||
{
|
||||
return cache.getHabitByPosition(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position)
|
||||
{
|
||||
return getItem(position).getId();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ModelObservable getObservable()
|
||||
{
|
||||
return observable;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<Habit> getSelected()
|
||||
{
|
||||
return new LinkedList<>(selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position,
|
||||
@Nullable View view,
|
||||
@Nullable ViewGroup parent)
|
||||
{
|
||||
if (listView == null) return null;
|
||||
|
||||
Habit habit = cache.getHabitByPosition(position);
|
||||
int score = cache.getScore(habit.getId());
|
||||
int checkmarks[] = cache.getCheckmarks(habit.getId());
|
||||
boolean selected = this.selected.contains(habit);
|
||||
|
||||
return listView.buildCardView((HabitCardView) view, habit, score,
|
||||
checkmarks, selected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether list of selected items is empty.
|
||||
*
|
||||
* @return true if selection is empty, false otherwise
|
||||
*/
|
||||
public boolean isSelectionEmpty()
|
||||
{
|
||||
return selected.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the adapter that it has been attached to a ListView.
|
||||
*/
|
||||
public void onAttached()
|
||||
{
|
||||
cache.onAttached();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCacheRefresh()
|
||||
{
|
||||
notifyDataSetChanged();
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the adapter that it has been detached from a ListView.
|
||||
*/
|
||||
public void onDetached()
|
||||
{
|
||||
cache.onDetached();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the order of habits on the adapter.
|
||||
* <p>
|
||||
* Note that this only has effect on the adapter cache. The database is not
|
||||
* modified, and the change is lost when the cache is refreshed. This method
|
||||
* is useful for making the ListView more responsive: while we wait for the
|
||||
* database operation to finish, the cache can be modified to reflect the
|
||||
* changes immediately.
|
||||
*
|
||||
* @param from the habit that should be moved
|
||||
* @param to the habit that currently occupies the desired position
|
||||
*/
|
||||
public void reorder(int from, int to)
|
||||
{
|
||||
cache.reorder(from, to);
|
||||
cache.refreshAllHabits(false);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HabitCardListView that this adapter will provide data for.
|
||||
* <p>
|
||||
* This object will be used to generated new HabitCardViews, upon demand.
|
||||
*
|
||||
* @param listView the HabitCardListView associated with this adapter
|
||||
*/
|
||||
public void setListView(@Nullable HabitCardListView listView)
|
||||
{
|
||||
this.listView = listView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects or deselects the item at a given position.
|
||||
*
|
||||
* @param position position of the item to be toggled
|
||||
*/
|
||||
public void toggleSelection(int position)
|
||||
{
|
||||
Habit h = getItem(position);
|
||||
int k = selected.indexOf(h);
|
||||
if (k < 0) selected.add(h);
|
||||
else selected.remove(h);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setShowArchived(boolean showArchived)
|
||||
{
|
||||
cache.setIncludeArchived(showArchived);
|
||||
cache.refreshAllHabits(true);
|
||||
}
|
||||
}
|
@ -0,0 +1,334 @@
|
||||
/*
|
||||
* 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.model;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.isoron.uhabits.HabitsApplication;
|
||||
import org.isoron.uhabits.commands.Command;
|
||||
import org.isoron.uhabits.commands.CommandRunner;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.tasks.BaseTask;
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* A HabitCardListCache fetches and keeps a cache of all the data necessary to
|
||||
* render a HabitCardListView.
|
||||
* <p>
|
||||
* This is needed since performing database lookups during scrolling can make
|
||||
* the ListView very slow. It also registers itself as an observer of the
|
||||
* models, in order to update itself automatically.
|
||||
*/
|
||||
public class HabitCardListCache implements CommandRunner.Listener
|
||||
{
|
||||
boolean includeArchived;
|
||||
|
||||
private int checkmarkCount;
|
||||
|
||||
private BaseTask currentFetchTask;
|
||||
|
||||
@Nullable
|
||||
private Listener listener;
|
||||
|
||||
@Nullable
|
||||
private Long lastLoadTimestamp;
|
||||
|
||||
@NonNull
|
||||
private CacheData data;
|
||||
|
||||
@Inject
|
||||
CommandRunner commandRunner;
|
||||
|
||||
public HabitCardListCache()
|
||||
{
|
||||
data = new CacheData();
|
||||
HabitsApplication.getComponent().inject(this);
|
||||
}
|
||||
|
||||
public int[] getCheckmarks(long habitId)
|
||||
{
|
||||
return data.checkmarks.get(habitId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the habits that occupies a certain position on the list.
|
||||
*
|
||||
* @param position the position of the habit
|
||||
* @return the habit at given position
|
||||
* @throws IndexOutOfBoundsException if position is not valid
|
||||
*/
|
||||
@NonNull
|
||||
public Habit getHabitByPosition(int position)
|
||||
{
|
||||
return data.habitsList.get(position);
|
||||
}
|
||||
|
||||
public int getHabitCount()
|
||||
{
|
||||
return data.habits.size();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getLastLoadTimestamp()
|
||||
{
|
||||
return lastLoadTimestamp;
|
||||
}
|
||||
|
||||
public int getScore(long habitId)
|
||||
{
|
||||
return data.scores.get(habitId);
|
||||
}
|
||||
|
||||
public boolean getIncludeArchived()
|
||||
{
|
||||
return includeArchived;
|
||||
}
|
||||
|
||||
public void onAttached()
|
||||
{
|
||||
// refreshAllHabits(true);
|
||||
if (lastLoadTimestamp == null) refreshAllHabits(true);
|
||||
commandRunner.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommandExecuted(@NonNull Command command,
|
||||
@Nullable Long refreshKey)
|
||||
{
|
||||
if (refreshKey == null) refreshAllHabits(true);
|
||||
else refreshHabit(refreshKey);
|
||||
}
|
||||
|
||||
public void onDetached()
|
||||
{
|
||||
// commandRunner.removeListener(this);
|
||||
}
|
||||
|
||||
public void refreshAllHabits(final boolean refreshScoresAndCheckmarks)
|
||||
{
|
||||
if (currentFetchTask != null) currentFetchTask.cancel(true);
|
||||
currentFetchTask = new RefreshAllHabitsTask(refreshScoresAndCheckmarks);
|
||||
currentFetchTask.execute();
|
||||
}
|
||||
|
||||
public void refreshHabit(final Long id)
|
||||
{
|
||||
new RefreshHabitTask(id).execute();
|
||||
}
|
||||
|
||||
public void reorder(int from, int to)
|
||||
{
|
||||
Habit fromHabit = data.habitsList.get(from);
|
||||
Habit toHabit = data.habitsList.get(to);
|
||||
|
||||
data.habitsList.remove(from);
|
||||
data.habitsList.add(to, fromHabit);
|
||||
|
||||
Habit.reorder(fromHabit, toHabit);
|
||||
}
|
||||
|
||||
public void setCheckmarkCount(int checkmarkCount)
|
||||
{
|
||||
this.checkmarkCount = checkmarkCount;
|
||||
}
|
||||
|
||||
public void setIncludeArchived(boolean includeArchived)
|
||||
{
|
||||
this.includeArchived = includeArchived;
|
||||
}
|
||||
|
||||
public void setListener(@Nullable Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when the data on the
|
||||
* cache has been modified.
|
||||
*/
|
||||
public interface Listener
|
||||
{
|
||||
/**
|
||||
* Called when the data on the cache has been modified.
|
||||
*/
|
||||
void onCacheRefresh();
|
||||
}
|
||||
|
||||
private class CacheData
|
||||
{
|
||||
@NonNull
|
||||
public HashMap<Long, Habit> habits;
|
||||
|
||||
@NonNull
|
||||
public List<Habit> habitsList;
|
||||
|
||||
@NonNull
|
||||
public HashMap<Long, int[]> checkmarks;
|
||||
|
||||
@NonNull
|
||||
public HashMap<Long, Integer> scores;
|
||||
|
||||
/**
|
||||
* Creates a new CacheData without any content.
|
||||
*/
|
||||
public CacheData()
|
||||
{
|
||||
habits = new HashMap<>();
|
||||
habitsList = new LinkedList<>();
|
||||
checkmarks = new HashMap<>();
|
||||
scores = new HashMap<>();
|
||||
}
|
||||
|
||||
public void copyCheckmarksFrom(@NonNull CacheData oldData)
|
||||
{
|
||||
int[] empty = new int[checkmarkCount];
|
||||
|
||||
for (Long id : habits.keySet())
|
||||
{
|
||||
if (oldData.checkmarks.containsKey(id))
|
||||
checkmarks.put(id, oldData.checkmarks.get(id));
|
||||
else checkmarks.put(id, empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void copyScoresFrom(@NonNull CacheData oldData)
|
||||
{
|
||||
for (Long id : habits.keySet())
|
||||
{
|
||||
if (oldData.scores.containsKey(id))
|
||||
scores.put(id, oldData.scores.get(id));
|
||||
else scores.put(id, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void fetchHabits()
|
||||
{
|
||||
habitsList = Habit.getAll(includeArchived);
|
||||
for (Habit h : habitsList)
|
||||
habits.put(h.getId(), h);
|
||||
}
|
||||
}
|
||||
|
||||
private class RefreshAllHabitsTask extends BaseTask
|
||||
{
|
||||
@NonNull
|
||||
private CacheData newData;
|
||||
|
||||
private final boolean refreshScoresAndCheckmarks;
|
||||
|
||||
public RefreshAllHabitsTask(boolean refreshScoresAndCheckmarks)
|
||||
{
|
||||
this.refreshScoresAndCheckmarks = refreshScoresAndCheckmarks;
|
||||
newData = new CacheData();
|
||||
}
|
||||
|
||||
private void commit()
|
||||
{
|
||||
data = newData;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInBackground()
|
||||
{
|
||||
newData.fetchHabits();
|
||||
newData.copyScoresFrom(data);
|
||||
newData.copyCheckmarksFrom(data);
|
||||
|
||||
commit();
|
||||
|
||||
if (!refreshScoresAndCheckmarks) return;
|
||||
|
||||
long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime());
|
||||
long dateFrom =
|
||||
dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay;
|
||||
|
||||
int current = 0;
|
||||
for (Habit h : newData.habitsList)
|
||||
{
|
||||
if (isCancelled()) return;
|
||||
|
||||
Long id = h.getId();
|
||||
newData.scores.put(id, h.scores.getTodayValue());
|
||||
newData.checkmarks.put(id,
|
||||
h.checkmarks.getValues(dateFrom, dateTo));
|
||||
|
||||
publishProgress(current++, newData.habits.size());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid)
|
||||
{
|
||||
if (isCancelled()) return;
|
||||
|
||||
lastLoadTimestamp = DateUtils.getStartOfToday();
|
||||
currentFetchTask = null;
|
||||
|
||||
if (listener != null) listener.onCacheRefresh();
|
||||
super.onPostExecute(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... values)
|
||||
{
|
||||
if (listener != null) listener.onCacheRefresh();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class RefreshHabitTask extends BaseTask
|
||||
{
|
||||
private final Long id;
|
||||
|
||||
public RefreshHabitTask(Long id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInBackground()
|
||||
{
|
||||
long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime());
|
||||
long dateFrom =
|
||||
dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay;
|
||||
|
||||
Habit h = Habit.get(id);
|
||||
if (h == null) return;
|
||||
|
||||
data.habits.put(id, h);
|
||||
data.scores.put(id, h.scores.getTodayValue());
|
||||
data.checkmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid)
|
||||
{
|
||||
if (listener != null) listener.onCacheRefresh();
|
||||
super.onPostExecute(null);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.model;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.isoron.uhabits.HabitsApplication;
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
import org.isoron.uhabits.utils.Preferences;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Provides a list of hints to be shown at the application startup, and takes
|
||||
* care of deciding when a new hint should be shown.
|
||||
*/
|
||||
public class HintList
|
||||
{
|
||||
@Inject
|
||||
Preferences prefs;
|
||||
|
||||
@NonNull
|
||||
private final String[] hints;
|
||||
|
||||
/**
|
||||
* Constructs a new list containing the provided hints.
|
||||
*
|
||||
* @param hints initial list of hints
|
||||
*/
|
||||
public HintList(@NonNull String hints[])
|
||||
{
|
||||
this.hints = hints;
|
||||
HabitsApplication.getComponent().inject(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new hint to be shown to the user.
|
||||
* <p>
|
||||
* The hint returned is marked as read on the list, and will not be returned
|
||||
* again. In case all hints have already been read, and there is nothing
|
||||
* left, returns null.
|
||||
*
|
||||
* @return the next hint to be shown, or null if none
|
||||
*/
|
||||
public String pop()
|
||||
{
|
||||
int next = prefs.getLastHintNumber() + 1;
|
||||
if (next >= hints.length) return null;
|
||||
|
||||
prefs.updateLastHint(next, DateUtils.getStartOfToday());
|
||||
return hints[next];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether it is time to show a new hint or not.
|
||||
*
|
||||
* @return true if hint should be shown, false otherwise
|
||||
*/
|
||||
public boolean shouldShow()
|
||||
{
|
||||
long lastHintTimestamp = prefs.getLastHintTimestamp();
|
||||
return (DateUtils.getStartOfToday() > lastHintTimestamp);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains model classes that are specific for ListHabitsActivity
|
||||
*/
|
||||
package org.isoron.uhabits.ui.habits.list.model;
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains classes for ListHabitsActivity.
|
||||
*/
|
||||
package org.isoron.uhabits.ui.habits.list;
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Checkmark;
|
||||
import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController;
|
||||
import org.isoron.uhabits.utils.InterfaceUtils;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class CheckmarkButtonView extends FrameLayout
|
||||
{
|
||||
private int color;
|
||||
|
||||
private int value;
|
||||
|
||||
@BindView(R.id.tvCheck)
|
||||
TextView tvCheck;
|
||||
|
||||
public CheckmarkButtonView(Context context)
|
||||
{
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public void setColor(int color)
|
||||
{
|
||||
this.color = color;
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void setController(final CheckmarkButtonController controller)
|
||||
{
|
||||
setOnClickListener(v -> controller.onClick());
|
||||
setOnLongClickListener(v -> controller.onLongClick());
|
||||
}
|
||||
|
||||
public void setValue(int value)
|
||||
{
|
||||
this.value = value;
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void toggle()
|
||||
{
|
||||
value = (value == Checkmark.CHECKED_EXPLICITLY ? Checkmark.UNCHECKED :
|
||||
Checkmark.CHECKED_EXPLICITLY);
|
||||
|
||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
addView(
|
||||
inflate(getContext(), R.layout.list_habits_card_checkmark, null));
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setWillNotDraw(false);
|
||||
setHapticFeedbackEnabled(false);
|
||||
|
||||
tvCheck.setTypeface(InterfaceUtils.getFontAwesome(getContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas)
|
||||
{
|
||||
int lowContrastColor = InterfaceUtils.getStyledColor(getContext(),
|
||||
R.attr.lowContrastTextColor);
|
||||
|
||||
if (value == Checkmark.CHECKED_EXPLICITLY)
|
||||
{
|
||||
tvCheck.setText(R.string.fa_check);
|
||||
tvCheck.setTextColor(color);
|
||||
}
|
||||
|
||||
if (value == Checkmark.CHECKED_IMPLICITLY)
|
||||
{
|
||||
tvCheck.setText(R.string.fa_check);
|
||||
tvCheck.setTextColor(lowContrastColor);
|
||||
}
|
||||
|
||||
if (value == Checkmark.UNCHECKED)
|
||||
{
|
||||
tvCheck.setText(R.string.fa_times);
|
||||
tvCheck.setTextColor(lowContrastColor);
|
||||
}
|
||||
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
}
|
@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.isoron.uhabits.HabitsApplication;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController;
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
import org.isoron.uhabits.utils.Preferences;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class CheckmarkPanelView extends LinearLayout
|
||||
{
|
||||
private static final int CHECKMARK_LEFT_TO_RIGHT = 0;
|
||||
|
||||
private static final int CHECKMARK_RIGHT_TO_LEFT = 1;
|
||||
|
||||
@Inject
|
||||
Preferences prefs;
|
||||
|
||||
private int checkmarkValues[];
|
||||
|
||||
private int nButtons;
|
||||
|
||||
private int color;
|
||||
|
||||
private Controller controller;
|
||||
|
||||
@NonNull
|
||||
private Habit habit;
|
||||
|
||||
public CheckmarkPanelView(Context context)
|
||||
{
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CheckmarkPanelView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public CheckmarkPanelView(Context context,
|
||||
AttributeSet attrs,
|
||||
int defStyleAttr)
|
||||
{
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
public CheckmarkButtonView getButton(int position)
|
||||
{
|
||||
return (CheckmarkButtonView) getChildAt(position);
|
||||
}
|
||||
|
||||
public void setCheckmarkValues(int[] checkmarkValues)
|
||||
{
|
||||
this.checkmarkValues = checkmarkValues;
|
||||
|
||||
if (this.nButtons != checkmarkValues.length)
|
||||
{
|
||||
this.nButtons = checkmarkValues.length;
|
||||
addCheckmarkButtons();
|
||||
}
|
||||
|
||||
setupCheckmarkButtons();
|
||||
}
|
||||
|
||||
public void setColor(int color)
|
||||
{
|
||||
this.color = color;
|
||||
setupCheckmarkButtons();
|
||||
}
|
||||
|
||||
public void setController(Controller controller)
|
||||
{
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
public void setHabit(@NonNull Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
private void addCheckmarkButtons()
|
||||
{
|
||||
removeAllViews();
|
||||
|
||||
for (int i = 0; i < nButtons; i++)
|
||||
addView(new CheckmarkButtonView(getContext()));
|
||||
}
|
||||
|
||||
private int getCheckmarkOrder()
|
||||
{
|
||||
if (isInEditMode()) return CHECKMARK_LEFT_TO_RIGHT;
|
||||
return prefs.shouldReverseCheckmarks() ? CHECKMARK_RIGHT_TO_LEFT :
|
||||
CHECKMARK_LEFT_TO_RIGHT;
|
||||
}
|
||||
|
||||
private CheckmarkButtonView indexToButton(int i)
|
||||
{
|
||||
int position = i;
|
||||
|
||||
if (getCheckmarkOrder() == CHECKMARK_RIGHT_TO_LEFT)
|
||||
position = nButtons - i - 1;
|
||||
|
||||
return (CheckmarkButtonView) getChildAt(position);
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
if (isInEditMode()) return;
|
||||
HabitsApplication.getComponent().inject(this);
|
||||
setWillNotDraw(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
float buttonWidth = getResources().getDimension(R.dimen.checkmarkWidth);
|
||||
float buttonHeight =
|
||||
getResources().getDimension(R.dimen.checkmarkHeight);
|
||||
|
||||
float width = buttonWidth * nButtons;
|
||||
|
||||
widthMeasureSpec =
|
||||
MeasureSpec.makeMeasureSpec((int) width, MeasureSpec.EXACTLY);
|
||||
heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) buttonHeight,
|
||||
MeasureSpec.EXACTLY);
|
||||
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
private void setupButtonControllers(long timestamp,
|
||||
CheckmarkButtonView buttonView)
|
||||
{
|
||||
if (controller == null) return;
|
||||
|
||||
CheckmarkButtonController buttonController =
|
||||
new CheckmarkButtonController(habit, timestamp);
|
||||
|
||||
buttonController.setListener(controller);
|
||||
buttonController.setView(buttonView);
|
||||
buttonView.setController(buttonController);
|
||||
}
|
||||
|
||||
private void setupCheckmarkButtons()
|
||||
{
|
||||
long timestamp = DateUtils.getStartOfToday();
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
|
||||
for (int i = 0; i < nButtons; i++)
|
||||
{
|
||||
CheckmarkButtonView buttonView = indexToButton(i);
|
||||
buttonView.setValue(checkmarkValues[i]);
|
||||
buttonView.setColor(color);
|
||||
setupButtonControllers(timestamp, buttonView);
|
||||
timestamp -= day;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Controller extends CheckmarkButtonController.Listener
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ListAdapter;
|
||||
|
||||
import com.mobeta.android.dslv.DragSortController;
|
||||
import com.mobeta.android.dslv.DragSortListView;
|
||||
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController;
|
||||
import org.isoron.uhabits.ui.habits.list.controllers.HabitCardController;
|
||||
import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter;
|
||||
|
||||
public class HabitCardListView extends DragSortListView
|
||||
{
|
||||
@Nullable
|
||||
private HabitCardListAdapter adapter;
|
||||
|
||||
@Nullable
|
||||
private Controller controller;
|
||||
|
||||
public HabitCardListView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
setFloatViewManager(new ViewManager());
|
||||
setDragEnabled(true);
|
||||
setLongClickable(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new HabitCardView to be eventually added to this list,
|
||||
* containing the given data.
|
||||
*
|
||||
* @param cardView an old HabitCardView that should be reused if possible,
|
||||
* possibly null
|
||||
* @param habit the habit for this card
|
||||
* @param score the current score for the habit
|
||||
* @param checkmarks the list of checkmark values to be included in the
|
||||
* card
|
||||
* @param selected true if the card is selected, false otherwise
|
||||
* @return the HabitCardView generated
|
||||
*/
|
||||
public View buildCardView(@Nullable HabitCardView cardView,
|
||||
@NonNull Habit habit,
|
||||
int score,
|
||||
int[] checkmarks,
|
||||
boolean selected)
|
||||
{
|
||||
if (cardView == null) cardView = new HabitCardView(getContext());
|
||||
|
||||
cardView.setHabit(habit);
|
||||
cardView.setSelected(selected);
|
||||
cardView.setCheckmarkValues(checkmarks);
|
||||
cardView.setScore(score);
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
HabitCardController cardController = new HabitCardController();
|
||||
cardController.setListener(controller);
|
||||
cardView.setController(cardController);
|
||||
cardController.setView(cardView);
|
||||
}
|
||||
|
||||
return cardView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAdapter(ListAdapter adapter)
|
||||
{
|
||||
this.adapter = (HabitCardListAdapter) adapter;
|
||||
super.setAdapter(adapter);
|
||||
}
|
||||
|
||||
public void setController(@Nullable Controller controller)
|
||||
{
|
||||
this.controller = controller;
|
||||
setDropListener(controller);
|
||||
setDragListener(controller);
|
||||
setOnItemClickListener(null);
|
||||
setOnLongClickListener(null);
|
||||
|
||||
if (controller == null) return;
|
||||
|
||||
setOnItemClickListener((p, v, pos, id) -> controller.onItemClick(pos));
|
||||
setOnItemLongClickListener((p, v, pos, id) -> {
|
||||
controller.onItemLongClick(pos);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public void toggleShowArchived()
|
||||
{
|
||||
// showArchived = !showArchived;
|
||||
// cache.setIncludeArchived(showArchived);
|
||||
// cache.refreshAllHabits(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow()
|
||||
{
|
||||
super.onAttachedToWindow();
|
||||
if (adapter != null) adapter.onAttached();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow()
|
||||
{
|
||||
if (adapter != null) adapter.onDetached();
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
public interface Controller extends CheckmarkButtonController.Listener,
|
||||
HabitCardController.Listener,
|
||||
DropListener,
|
||||
DragListener
|
||||
{
|
||||
void onItemClick(int pos);
|
||||
|
||||
void onItemLongClick(int pos);
|
||||
}
|
||||
|
||||
private class ViewManager extends DragSortController
|
||||
{
|
||||
public ViewManager()
|
||||
{
|
||||
super(HabitCardListView.this);
|
||||
setRemoveEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateFloatView(int position)
|
||||
{
|
||||
if (adapter == null) return null;
|
||||
return adapter.getView(position, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyFloatView(View floatView)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,216 @@
|
||||
/*
|
||||
* 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.views;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
import org.isoron.uhabits.utils.ColorUtils;
|
||||
import org.isoron.uhabits.views.RingView;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
import static org.isoron.uhabits.utils.InterfaceUtils.getStyledColor;
|
||||
import static org.isoron.uhabits.utils.InterfaceUtils.getStyledDrawable;
|
||||
|
||||
public class HabitCardView extends FrameLayout
|
||||
{
|
||||
private Habit habit;
|
||||
|
||||
@BindView(R.id.checkmarkPanel)
|
||||
CheckmarkPanelView checkmarkPanel;
|
||||
|
||||
@BindView(R.id.innerFrame)
|
||||
LinearLayout innerFrame;
|
||||
|
||||
@BindView(R.id.label)
|
||||
TextView label;
|
||||
|
||||
@BindView(R.id.scoreRing)
|
||||
RingView scoreRing;
|
||||
|
||||
private final Context context = getContext();
|
||||
|
||||
public HabitCardView(Context context)
|
||||
{
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public HabitCardView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public HabitCardView(Context context, AttributeSet attrs, int defStyleAttr)
|
||||
{
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
public void setCheckmarkValues(int checkmarks[])
|
||||
{
|
||||
checkmarkPanel.setCheckmarkValues(checkmarks);
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void setController(Controller controller)
|
||||
{
|
||||
checkmarkPanel.setController(null);
|
||||
if (controller == null) return;
|
||||
|
||||
checkmarkPanel.setController(controller);
|
||||
}
|
||||
|
||||
public void setHabit(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
int color = getActiveColor(habit);
|
||||
|
||||
label.setText(habit.name);
|
||||
label.setTextColor(color);
|
||||
scoreRing.setColor(color);
|
||||
checkmarkPanel.setColor(color);
|
||||
checkmarkPanel.setHabit(habit);
|
||||
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void setScore(int score)
|
||||
{
|
||||
float percentage = (float) score / Score.MAX_VALUE;
|
||||
scoreRing.setPercentage(percentage);
|
||||
scoreRing.setPrecision(1.0f / 16);
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(boolean isSelected)
|
||||
{
|
||||
super.setSelected(isSelected);
|
||||
updateBackground(isSelected);
|
||||
}
|
||||
|
||||
public void triggerRipple(final float x, final float y)
|
||||
{
|
||||
final Drawable background = innerFrame.getBackground();
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21) background.setHotspot(x, y);
|
||||
background.setState(new int[]{
|
||||
android.R.attr.state_pressed, android.R.attr.state_enabled
|
||||
});
|
||||
new Handler().postDelayed(() -> background.setState(new int[]{}), 25);
|
||||
}
|
||||
|
||||
private int getActiveColor(Habit habit)
|
||||
{
|
||||
int mediumContrastColor =
|
||||
getStyledColor(context, R.attr.mediumContrastTextColor);
|
||||
int activeColor = ColorUtils.getColor(context, habit.color);
|
||||
if (habit.isArchived()) activeColor = mediumContrastColor;
|
||||
|
||||
return activeColor;
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
inflate(context, R.layout.list_habits_card, this);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
innerFrame.setOnTouchListener((v, event) -> {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
v.getBackground().setHotspot(event.getX(), event.getY());
|
||||
return false;
|
||||
});
|
||||
|
||||
if (isInEditMode()) initEditMode();
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void initEditMode()
|
||||
{
|
||||
String habits[] = {
|
||||
"Wake up early",
|
||||
"Wash dishes",
|
||||
"Exercise",
|
||||
"Meditate",
|
||||
"Play guitar",
|
||||
"Wash clothes",
|
||||
"Get a haircut"
|
||||
};
|
||||
|
||||
Random rand = new Random();
|
||||
int color = ColorUtils.CSV_PALETTE[rand.nextInt(10)];
|
||||
int[] values = {
|
||||
rand.nextInt(3),
|
||||
rand.nextInt(3),
|
||||
rand.nextInt(3),
|
||||
rand.nextInt(3),
|
||||
rand.nextInt(3)
|
||||
};
|
||||
|
||||
label.setText(habits[rand.nextInt(habits.length)]);
|
||||
label.setTextColor(color);
|
||||
scoreRing.setColor(color);
|
||||
scoreRing.setPercentage(rand.nextFloat());
|
||||
checkmarkPanel.setColor(color);
|
||||
checkmarkPanel.setCheckmarkValues(values);
|
||||
}
|
||||
|
||||
private void updateBackground(boolean isSelected)
|
||||
{
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
{
|
||||
if (isSelected)
|
||||
innerFrame.setBackgroundResource(R.drawable.selected_box);
|
||||
else innerFrame.setBackgroundResource(R.drawable.ripple);
|
||||
}
|
||||
else
|
||||
{
|
||||
Drawable background;
|
||||
|
||||
if (isSelected) background =
|
||||
getStyledDrawable(context, R.attr.selectedBackground);
|
||||
else background = getStyledDrawable(context, R.attr.cardBackground);
|
||||
|
||||
innerFrame.setBackgroundDrawable(background);
|
||||
}
|
||||
}
|
||||
|
||||
public interface Controller extends CheckmarkPanelView.Controller
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
public class HeaderView extends LinearLayout
|
||||
{
|
||||
private static final int CHECKMARK_LEFT_TO_RIGHT = 0;
|
||||
|
||||
private static final int CHECKMARK_RIGHT_TO_LEFT = 1;
|
||||
|
||||
private final Context context;
|
||||
|
||||
public HeaderView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private void createButtons()
|
||||
{
|
||||
removeAllViews();
|
||||
GregorianCalendar day = DateUtils.getStartOfTodayCalendar();
|
||||
|
||||
for (int i = 0; i < getButtonCount(); i++)
|
||||
{
|
||||
int position = 0;
|
||||
|
||||
if (getCheckmarkOrder() == CHECKMARK_LEFT_TO_RIGHT) position = i;
|
||||
|
||||
View tvDay =
|
||||
inflate(context, R.layout.list_habits_header_checkmark, null);
|
||||
TextView btCheck = (TextView) tvDay.findViewById(R.id.tvCheck);
|
||||
btCheck.setText(DateUtils.formatHeaderDate(day));
|
||||
addView(tvDay, position);
|
||||
day.add(GregorianCalendar.DAY_OF_MONTH, -1);
|
||||
}
|
||||
}
|
||||
|
||||
private int getButtonCount()
|
||||
{
|
||||
float labelWidth = getResources().getDimension(R.dimen.habitNameWidth);
|
||||
float buttonWidth = getResources().getDimension(R.dimen.checkmarkWidth);
|
||||
return Math.max(0,
|
||||
(int) ((getMeasuredWidth() - labelWidth) / buttonWidth));
|
||||
}
|
||||
|
||||
private int getCheckmarkOrder()
|
||||
{
|
||||
if (isInEditMode()) return CHECKMARK_LEFT_TO_RIGHT;
|
||||
|
||||
SharedPreferences prefs =
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
boolean reverse =
|
||||
prefs.getBoolean("pref_checkmark_reverse_order", false);
|
||||
return reverse ? CHECKMARK_RIGHT_TO_LEFT : CHECKMARK_LEFT_TO_RIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
createButtons();
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains classes for the IntroActivity.
|
||||
*/
|
||||
package org.isoron.uhabits.ui.intro;
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains classes for the SettingsActivity.
|
||||
*/
|
||||
package org.isoron.uhabits.ui.settings;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue