diff --git a/app/build.gradle b/app/build.gradle
index 6bdb20d23..b6e28d599 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -4,13 +4,13 @@ apply plugin: 'me.tatarka.retrolambda'
apply plugin: 'jacoco'
android {
- compileSdkVersion 23
- buildToolsVersion "23.0.3"
+ compileSdkVersion 25
+ buildToolsVersion "25.0.2"
defaultConfig {
applicationId "org.isoron.uhabits"
minSdkVersion 15
- targetSdkVersion 23
+ targetSdkVersion 25
buildConfigField "Integer", "databaseVersion", "15"
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
@@ -53,7 +53,7 @@ dependencies {
androidTestApt 'com.google.dagger:dagger-compiler:2.2'
- androidTestCompile 'com.android.support:support-annotations:23.3.0'
+ androidTestCompile 'com.android.support:support-annotations:25.1.0'
androidTestCompile 'com.android.support.test:rules:0.5'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.google.auto.factory:auto-factory:1.0-beta3'
@@ -64,10 +64,10 @@ dependencies {
apt 'com.google.dagger:dagger-compiler:2.2'
apt 'com.jakewharton:butterknife-compiler:8.0.1'
- compile 'com.android.support:appcompat-v7:23.3.0'
- compile 'com.android.support:design:23.3.0'
- compile 'com.android.support:preference-v14:23.3.0'
- compile 'com.android.support:support-v4:23.3.0'
+ compile 'com.android.support:appcompat-v7:25.1.0'
+ compile 'com.android.support:design:25.1.0'
+ compile 'com.android.support:preference-v14:25.1.0'
+ compile 'com.android.support:support-v4:25.1.0'
compile 'com.getpebble:pebblekit:3.0.0'
compile 'com.github.paolorotolo:appintro:3.4.0'
compile 'com.google.auto.factory:auto-factory:1.0-beta3'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ee57323d5..1bab9be06 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -223,5 +223,15 @@
+
+
+
+
diff --git a/app/src/main/java/org/isoron/uhabits/activities/BaseScreen.java b/app/src/main/java/org/isoron/uhabits/activities/BaseScreen.java
index 1954c7c42..64621414c 100644
--- a/app/src/main/java/org/isoron/uhabits/activities/BaseScreen.java
+++ b/app/src/main/java/org/isoron/uhabits/activities/BaseScreen.java
@@ -40,6 +40,7 @@ import java.io.*;
import static android.os.Build.VERSION.*;
import static android.os.Build.VERSION_CODES.*;
+import static android.support.v4.content.FileProvider.*;
/**
* Base class for all screens in the application.
@@ -50,6 +51,8 @@ import static android.os.Build.VERSION_CODES.*;
*/
public class BaseScreen
{
+ public static final int REQUEST_CREATE_DOCUMENT = 1;
+
protected BaseActivity activity;
@Nullable
@@ -230,11 +233,14 @@ public class BaseScreen
public void showSendFileScreen(@NonNull String archiveFilename)
{
+ File file = new File(archiveFilename);
+ Uri fileUri = getUriForFile(activity, "org.isoron.uhabits", file);
+
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setType("application/zip");
- intent.putExtra(Intent.EXTRA_STREAM,
- Uri.fromFile(new File(archiveFilename)));
+ intent.putExtra(Intent.EXTRA_STREAM, fileUri);
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.startActivity(intent);
}
diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java
index d5529c2a1..deb1b3649 100644
--- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java
+++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java
@@ -25,6 +25,7 @@ import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.*;
import org.isoron.uhabits.activities.habits.list.model.*;
import org.isoron.uhabits.preferences.*;
+import org.isoron.uhabits.utils.*;
/**
* Activity that allows the user to see and modify the list of habits.
@@ -43,6 +44,8 @@ public class ListHabitsActivity extends BaseActivity
private Preferences prefs;
+ private MidnightTimer midnightTimer;
+
public ListHabitsComponent getListHabitsComponent()
{
return component;
@@ -77,6 +80,8 @@ public class ListHabitsActivity extends BaseActivity
screen.setSelectionMenu(selectionMenu);
rootView.setController(controller, selectionMenu);
+ midnightTimer = component.getMidnightTimer();
+
setScreen(screen);
controller.onStartup();
}
@@ -84,6 +89,7 @@ public class ListHabitsActivity extends BaseActivity
@Override
protected void onPause()
{
+ midnightTimer.onPause();
screen.onDettached();
adapter.cancelRefresh();
super.onPause();
@@ -95,6 +101,7 @@ public class ListHabitsActivity extends BaseActivity
adapter.refresh();
screen.onAttached();
rootView.postInvalidate();
+ midnightTimer.onResume();
if (prefs.getTheme() == ThemeSwitcher.THEME_DARK &&
prefs.isPureBlackEnabled() != pureBlack)
diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsComponent.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsComponent.java
index 658d6c573..554c22a3b 100644
--- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsComponent.java
+++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsComponent.java
@@ -23,13 +23,14 @@ import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.*;
import org.isoron.uhabits.activities.habits.list.controllers.*;
import org.isoron.uhabits.activities.habits.list.model.*;
+import org.isoron.uhabits.utils.*;
import dagger.*;
@ActivityScope
@Component(modules = { ActivityModule.class },
dependencies = { AppComponent.class })
-public interface ListHabitsComponent extends ActivityComponent
+public interface ListHabitsComponent
{
CheckmarkButtonControllerFactory getCheckmarkButtonControllerFactory();
@@ -44,4 +45,6 @@ public interface ListHabitsComponent extends ActivityComponent
ListHabitsScreen getScreen();
ListHabitsSelectionMenu getSelectionMenu();
+
+ MidnightTimer getMidnightTimer();
}
diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java
index 875124f73..a52c4b4d6 100644
--- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java
+++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java
@@ -133,7 +133,8 @@ public class ListHabitsController
taskRunner.execute(() -> habitList.reorder(from, to));
}
- public void onImportData(@NonNull File file)
+ public void onImportData(@NonNull File file,
+ @NonNull OnFinishedListener finishedListener)
{
taskRunner.execute(importTaskFactory.create(file, result -> {
switch (result)
@@ -151,6 +152,8 @@ public class ListHabitsController
screen.showMessage(R.string.could_not_import);
break;
}
+
+ finishedListener.onFinish();
}));
}
@@ -213,4 +216,9 @@ public class ListHabitsController
prefs.updateLastHint(-1, DateUtils.getStartOfToday());
screen.showIntroScreen();
}
+
+ public interface OnFinishedListener
+ {
+ void onFinish();
+ }
}
diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java
index 3fb92e817..045505a90 100644
--- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java
+++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java
@@ -19,7 +19,9 @@
package org.isoron.uhabits.activities.habits.list;
+import android.app.*;
import android.content.*;
+import android.net.*;
import android.support.annotation.*;
import org.isoron.uhabits.*;
@@ -31,24 +33,32 @@ import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.io.*;
import org.isoron.uhabits.models.*;
+import org.isoron.uhabits.utils.*;
import java.io.*;
import javax.inject.*;
+import static android.os.Build.VERSION.*;
+import static android.os.Build.VERSION_CODES.*;
+
@ActivityScope
public class ListHabitsScreen extends BaseScreen
implements CommandRunner.Listener
{
- public static final int RESULT_BUG_REPORT = 4;
+ public static final int RESULT_IMPORT_DATA = 1;
public static final int RESULT_EXPORT_CSV = 2;
public static final int RESULT_EXPORT_DB = 3;
+ public static final int RESULT_BUG_REPORT = 4;
+
public static final int RESULT_REPAIR_DB = 5;
- public static final int RESULT_IMPORT_DATA = 1;
+ public static final int REQUEST_OPEN_DOCUMENT = 6;
+
+ public static final int REQUEST_SETTINGS = 7;
@Nullable
private ListHabitsController controller;
@@ -125,6 +135,15 @@ public class ListHabitsScreen extends BaseScreen
@Override
public void onResult(int requestCode, int resultCode, Intent data)
+ {
+ if (requestCode == REQUEST_OPEN_DOCUMENT)
+ onOpenDocumentResult(resultCode, data);
+
+ if (requestCode == REQUEST_SETTINGS)
+ onSettingsResult(resultCode);
+ }
+
+ private void onSettingsResult(int resultCode)
{
if (controller == null) return;
@@ -152,6 +171,30 @@ public class ListHabitsScreen extends BaseScreen
}
}
+ private void onOpenDocumentResult(int resultCode, Intent data)
+ {
+ if (controller == null) return;
+ if (resultCode != Activity.RESULT_OK) return;
+
+ try
+ {
+ Uri uri = data.getData();
+ ContentResolver cr = activity.getContentResolver();
+ InputStream is = cr.openInputStream(uri);
+
+ File cacheDir = activity.getExternalCacheDir();
+ File tempFile = File.createTempFile("import", "", cacheDir);
+
+ FileUtils.copy(is, tempFile);
+ controller.onImportData(tempFile, () -> tempFile.delete());
+ }
+ catch (IOException e)
+ {
+ showMessage(R.string.could_not_import);
+ e.printStackTrace();
+ }
+ }
+
public void setController(@Nullable ListHabitsController controller)
{
this.controller = controller;
@@ -208,6 +251,21 @@ public class ListHabitsScreen extends BaseScreen
}
public void showImportScreen()
+ {
+ if (SDK_INT < KITKAT)
+ {
+ showImportScreenPreKitKat();
+ return;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("*/*");
+
+ activity.startActivityForResult(intent, REQUEST_OPEN_DOCUMENT);
+ }
+
+ public void showImportScreenPreKitKat()
{
File dir = dirFinder.findStorageDir(null);
@@ -220,7 +278,8 @@ public class ListHabitsScreen extends BaseScreen
FilePickerDialog picker = filePickerDialogFactory.create(dir);
if (controller != null)
- picker.setListener(file -> controller.onImportData(file));
+ picker.setListener(file -> controller.onImportData(file, () -> {}));
+
activity.showDialog(picker.getDialog());
}
@@ -233,7 +292,7 @@ public class ListHabitsScreen extends BaseScreen
public void showSettingsScreen()
{
Intent intent = intentFactory.startSettingsActivity(activity);
- activity.startActivityForResult(intent, 0);
+ activity.startActivityForResult(intent, REQUEST_SETTINGS);
}
public void toggleNightMode()
diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/model/HabitCardListAdapter.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/model/HabitCardListAdapter.java
index 7b91c3df8..55656b542 100644
--- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/model/HabitCardListAdapter.java
+++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/model/HabitCardListAdapter.java
@@ -28,6 +28,7 @@ import org.isoron.uhabits.activities.habits.list.*;
import org.isoron.uhabits.activities.habits.list.views.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.preferences.*;
+import org.isoron.uhabits.utils.*;
import java.util.*;
@@ -42,7 +43,7 @@ import javax.inject.*;
@ActivityScope
public class HabitCardListAdapter
extends RecyclerView.Adapter
- implements HabitCardListCache.Listener
+ implements HabitCardListCache.Listener, MidnightTimer.MidnightListener
{
@NonNull
private ModelObservable observable;
@@ -59,15 +60,20 @@ public class HabitCardListAdapter
@NonNull
private Preferences preferences;
+ private final MidnightTimer midnightTimer;
+
@Inject
public HabitCardListAdapter(@NonNull HabitCardListCache cache,
- @NonNull Preferences preferences)
+ @NonNull Preferences preferences,
+ @NonNull MidnightTimer midnightTimer)
{
this.preferences = preferences;
this.selected = new LinkedList<>();
this.observable = new ModelObservable();
this.cache = cache;
+ this.midnightTimer = midnightTimer;
+
cache.setListener(this);
cache.setCheckmarkCount(ListHabitsRootView.MAX_CHECKMARK_COUNT);
cache.setOrder(preferences.getDefaultOrder());
@@ -75,6 +81,12 @@ public class HabitCardListAdapter
setHasStableIds(true);
}
+ @Override
+ public void atMidnight()
+ {
+ cache.refreshAllHabits();
+ }
+
public void cancelRefresh()
{
cache.cancelTasks();
@@ -148,6 +160,7 @@ public class HabitCardListAdapter
public void onAttached()
{
cache.onAttached();
+ midnightTimer.addListener(this);
}
@Override
@@ -180,6 +193,7 @@ public class HabitCardListAdapter
public void onDetached()
{
cache.onDetached();
+ midnightTimer.removeListener(this);
}
@Override
diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.java
index 70ed6ec91..8f9d7c4d0 100644
--- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.java
+++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.java
@@ -26,12 +26,14 @@ import android.view.*;
import android.widget.*;
import org.isoron.uhabits.*;
+import org.isoron.uhabits.activities.habits.list.*;
import org.isoron.uhabits.preferences.*;
import org.isoron.uhabits.utils.*;
import java.util.*;
-public class HeaderView extends LinearLayout implements Preferences.Listener
+public class HeaderView extends LinearLayout
+ implements Preferences.Listener, MidnightTimer.MidnightListener
{
private final Context context;
@@ -40,6 +42,9 @@ public class HeaderView extends LinearLayout implements Preferences.Listener
@Nullable
private Preferences prefs;
+ @Nullable
+ private MidnightTimer midnightTimer;
+
public HeaderView(Context context, AttributeSet attrs)
{
super(context, attrs);
@@ -56,6 +61,18 @@ public class HeaderView extends LinearLayout implements Preferences.Listener
HabitsApplication app = (HabitsApplication) appContext;
prefs = app.getComponent().getPreferences();
}
+
+ if (context instanceof ListHabitsActivity)
+ {
+ ListHabitsActivity activity = (ListHabitsActivity) context;
+ midnightTimer = activity.getListHabitsComponent().getMidnightTimer();
+ }
+ }
+
+ @Override
+ public void atMidnight()
+ {
+ post(() -> createButtons());
}
@Override
@@ -75,11 +92,13 @@ public class HeaderView extends LinearLayout implements Preferences.Listener
{
super.onAttachedToWindow();
if (prefs != null) prefs.addListener(this);
+ if (midnightTimer != null) midnightTimer.addListener(this);
}
@Override
protected void onDetachedFromWindow()
{
+ if (midnightTimer != null) midnightTimer.removeListener(this);
if (prefs != null) prefs.removeListener(this);
super.onDetachedFromWindow();
}
diff --git a/app/src/main/java/org/isoron/uhabits/utils/DateUtils.java b/app/src/main/java/org/isoron/uhabits/utils/DateUtils.java
index c85f90df2..cce452145 100644
--- a/app/src/main/java/org/isoron/uhabits/utils/DateUtils.java
+++ b/app/src/main/java/org/isoron/uhabits/utils/DateUtils.java
@@ -187,6 +187,11 @@ public abstract class DateUtils
return getStartOfDay(DateUtils.getLocalTime());
}
+ public static long millisecondsUntilTomorrow()
+ {
+ return getStartOfToday() + millisecondsInOneDay - getLocalTime();
+ }
+
public static GregorianCalendar getStartOfTodayCalendar()
{
return getCalendar(getStartOfToday());
diff --git a/app/src/main/java/org/isoron/uhabits/utils/MidnightTimer.java b/app/src/main/java/org/isoron/uhabits/utils/MidnightTimer.java
new file mode 100644
index 000000000..30021ae71
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/utils/MidnightTimer.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 Álinson Santos Xavier
+ *
+ * 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 .
+ */
+
+package org.isoron.uhabits.utils;
+
+import org.isoron.uhabits.activities.*;
+
+import java.util.*;
+import java.util.concurrent.*;
+
+import javax.inject.*;
+
+/**
+ * A class that emits events when a new day starts.
+ */
+@ActivityScope
+public class MidnightTimer
+{
+ private final List listeners;
+
+ private ScheduledExecutorService executor;
+
+ @Inject
+ public MidnightTimer()
+ {
+ this.listeners = new LinkedList<>();
+ }
+
+ public synchronized void addListener(MidnightListener listener)
+ {
+ this.listeners.add(listener);
+ }
+
+ public synchronized void onPause()
+ {
+ executor.shutdownNow();
+ }
+
+ public synchronized void onResume()
+ {
+ executor = Executors.newSingleThreadScheduledExecutor();
+ executor.scheduleAtFixedRate(() -> notifyListeners(),
+ DateUtils.millisecondsUntilTomorrow() + 1000,
+ DateUtils.millisecondsInOneDay, TimeUnit.MILLISECONDS);
+ }
+
+ public synchronized void removeListener(MidnightListener listener)
+ {
+ this.listeners.remove(listener);
+ }
+
+ private synchronized void notifyListeners()
+ {
+ for (MidnightListener l : listeners) l.atMidnight();
+ }
+
+ public interface MidnightListener
+ {
+ void atMidnight();
+ }
+}
diff --git a/app/src/main/res/values/pickers.xml b/app/src/main/res/values/pickers.xml
index 354c3ae89..c83a58066 100644
--- a/app/src/main/res/values/pickers.xml
+++ b/app/src/main/res/values/pickers.xml
@@ -41,12 +41,12 @@
14sp
6dip
4dip
- 96dip
+ 80dip
48dip
48dip
24dip
- 270dip
- 270dp
+ 250dip
+ 250dp
30dp
155dp
270dp
diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 000000000..d97286ddf
--- /dev/null
+++ b/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java b/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java
index 5c4b413ae..da6c04587 100644
--- a/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java
+++ b/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java
@@ -38,10 +38,11 @@ import org.junit.runners.*;
import java.io.*;
+import static org.isoron.uhabits.activities.habits.list.ListHabitsScreen.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.eq;
@RunWith(JUnit4.class)
public class ListHabitsScreenTest extends BaseUnitTest
@@ -124,28 +125,28 @@ public class ListHabitsScreenTest extends BaseUnitTest
@Test
public void testOnResult_bugReport()
{
- screen.onResult(0, ListHabitsScreen.RESULT_BUG_REPORT, null);
+ screen.onResult(REQUEST_SETTINGS, RESULT_BUG_REPORT, null);
verify(controller).onSendBugReport();
}
@Test
public void testOnResult_exportCSV()
{
- screen.onResult(0, ListHabitsScreen.RESULT_EXPORT_CSV, null);
+ screen.onResult(REQUEST_SETTINGS, RESULT_EXPORT_CSV, null);
verify(controller).onExportCSV();
}
@Test
public void testOnResult_exportDB()
{
- screen.onResult(0, ListHabitsScreen.RESULT_EXPORT_DB, null);
+ screen.onResult(REQUEST_SETTINGS, RESULT_EXPORT_DB, null);
verify(controller).onExportDB();
}
@Test
public void testOnResult_importData()
{
- screen.onResult(0, ListHabitsScreen.RESULT_IMPORT_DATA, null);
+ screen.onResult(REQUEST_SETTINGS, RESULT_IMPORT_DATA, null);
testShowImportScreen();
}
diff --git a/circle.yml b/circle.yml
index 243168196..f0e7ffa46 100644
--- a/circle.yml
+++ b/circle.yml
@@ -1,7 +1,9 @@
dependencies:
pre:
- echo y | android update sdk --no-ui --all --filter "tools"
- - echo y | android update sdk --no-ui --all --filter "build-tools-23.0.3"
+ - echo y | android update sdk --no-ui --all --filter "android-25"
+ - echo y | android update sdk --no-ui --all --filter "build-tools-25.0.2"
+ - echo y | android update sdk --no-ui --all --filter "extra-android-m2repository"
checkout:
post:
- git submodule sync
@@ -24,4 +26,4 @@ general:
- poeditor
machine:
java:
- version: oraclejdk8
\ No newline at end of file
+ version: oraclejdk8