resoved conflict

pull/316/head
Wei Lun 8 years ago
commit b26e7deba2

3
.gitignore vendored

@ -3,6 +3,7 @@
*.class *.class
*.dex *.dex
*.iml *.iml
*.local
*.local.* *.local.*
*.swp *.swp
*.trace *.trace
@ -22,3 +23,5 @@ gen/
local.properties local.properties
crowdin.yaml crowdin.yaml
local local
tmp/
secret/

@ -1,13 +1,17 @@
# Loop Habit Tracker # Loop Habit Tracker
<a href="https://circleci.com/gh/iSoron/uhabits/tree/dev"> <a href="https://build.loophabits.org/project.html?projectId=LoopHabitTracker&tab=projectOverview&guest=1">
<img src="https://img.shields.io/circleci/project/iSoron/uhabits/dev.svg"> <img src="https://img.shields.io/teamcity/https/build.loophabits.org/s/release.svg">
</a> </a>
<!-- <a href="https://build.loophabits.org/project.html?projectId=LoopHabitTracker&tab=preport_project1_Coverage__core_&guest=1">
<a href="https://codecov.io/github/iSoron/uhabits?branch=dev"> <img src="https://build.loophabits.org/app/rest/builds/buildType(id:release)/artifacts/content/coverage-badge.svg?guest=1" />
<img src="https://img.shields.io/codecov/c/github/iSoron/uhabits.svg" alt="Coverage via Codecov" /> </a>
<a href="https://github.com/iSoron/uhabits/releases/latest">
<img src="https://img.shields.io/badge/apk-stable-FF4081.svg" />
</a>
<a href="https://build.loophabits.org/app/rest/builds/buildType(id:release)/artifacts/content/loop-dev.apk?guest=1">
<img src="https://img.shields.io/badge/apk-nightly-FF4081.svg" />
</a> </a>
-->
Loop is a simple Android app that helps you create and maintain good habits, Loop is a simple Android app that helps you create and maintain good habits,
allowing you to achieve your long-term goals. Detailed graphs and statistics allowing you to achieve your long-term goals. Detailed graphs and statistics
@ -15,8 +19,8 @@ show you how your habits improved over time. It is completely ad-free and open
source. source.
<p align="center"> <p align="center">
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" height="75px"/></a> <a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" height="75px"/></a>
<a href="http://f-droid.org/app/org.isoron.uhabits"><img alt="Git if on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a> <a href="http://f-droid.org/app/org.isoron.uhabits"><img alt="Git if on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a>
</p> </p>
## Screenshots ## Screenshots
@ -84,7 +88,7 @@ contribute, even if you are not a software developer.
* **Translate the app into your own language.** If you are not a native English * **Translate the app into your own language.** If you are not a native English
speaker, and would like to see the app translated into your own language, speaker, and would like to see the app translated into your own language,
please join our [open translation project at POEditor][poedit]. If the translation please join our [open translation project][poedit]. If the translation
is already completed, you are also very welcome to join and proofread it. is already completed, you are also very welcome to join and proofread it.
* **Write some code.** If you are an Android developer, you are very welcome to * **Write some code.** If you are an Android developer, you are very welcome to
@ -121,7 +125,7 @@ contribute, even if you are not a software developer.
[screen4th]: screenshots/thumbs/uhabits4.png [screen4th]: screenshots/thumbs/uhabits4.png
[screen5th]: screenshots/thumbs/uhabits5.png [screen5th]: screenshots/thumbs/uhabits5.png
[screen6th]: screenshots/thumbs/uhabits6.png [screen6th]: screenshots/thumbs/uhabits6.png
[poedit]: https://poeditor.com/join/project/8DWX5pfjS0 [poedit]: http://translate.loophabits.org
[playstore]: https://play.google.com/store/apps/details?id=org.isoron.uhabits [playstore]: https://play.google.com/store/apps/details?id=org.isoron.uhabits
[releases]: https://github.com/iSoron/uhabits/releases [releases]: https://github.com/iSoron/uhabits/releases
[fdroid]: http://f-droid.org/app/org.isoron.uhabits [fdroid]: http://f-droid.org/app/org.isoron.uhabits

@ -0,0 +1 @@
/build

@ -0,0 +1,46 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion COMPILE_SDK_VERSION as Integer
buildToolsVersion BUILD_TOOLS_VERSION
defaultConfig {
minSdkVersion MIN_SDK_VERSION as Integer
targetSdkVersion TARGET_SDK_VERSION as Integer
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation "com.google.dagger:dagger:$DAGGER_VERSION"
implementation "com.android.support:design:$SUPPORT_LIBRARY_VERSION"
implementation "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION"
implementation "org.apache.commons:commons-lang3:3.5"
annotationProcessor "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
androidTestImplementation "com.google.dagger:dagger:$DAGGER_VERSION"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
testImplementation "junit:junit:4.12"
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
}

@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /gemini-b/opt/android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

@ -0,0 +1,2 @@
<manifest package="org.isoron.androidbase"
xmlns:android="http://schemas.android.com/apk/res/android"/>

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com> * Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
* *
* This file is part of Loop Habit Tracker. * This file is part of Loop Habit Tracker.
* *
@ -17,75 +17,32 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.activities; package org.isoron.androidbase;
import android.content.*; import android.content.*;
import android.os.*; import android.os.*;
import android.support.annotation.*; import android.support.annotation.*;
import android.view.*; import android.view.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.utils.*;
import java.io.*; import java.io.*;
import java.lang.Process; import java.text.*;
import java.util.*; import java.util.*;
import javax.inject.*; import javax.inject.*;
/** public class AndroidBugReporter
* Base class for all systems class in the application.
* <p>
* Classes derived from BaseSystem are responsible for handling events and
* sending requests to the Android operating system. Examples include capturing
* a bug report, obtaining device information, or requesting runtime
* permissions.
*/
@ActivityScope
public class BaseSystem
{ {
private Context context; private final Context context;
@Inject @Inject
public BaseSystem(@ActivityContext Context context) public AndroidBugReporter(@NonNull @AppContext Context context)
{ {
this.context = context; this.context = context;
} }
/** /**
* Captures a bug report and saves it to a file in the SD card. * Captures and returns a bug report. The bug report contains some device
* <p> * information and the logcat.
* The contents of the file are generated by the method {@link
* #getBugReport()}. The file is saved in the apps's external private
* storage.
*
* @return the generated file.
* @throws IOException when I/O errors occur.
*/
@NonNull
public File dumpBugReportToFile() throws IOException
{
String date =
DateFormats.getBackupDateFormat().format(DateUtils.getLocalTime());
if (context == null) throw new RuntimeException(
"application context should not be null");
File dir = FileUtils.getFilesDir(context, "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;
}
/**
* Captures and returns a bug report.
* <p>
* The bug report contains some device information and the logcat.
* *
* @return a String containing the bug report. * @return a String containing the bug report.
* @throws IOException when any I/O error occur. * @throws IOException when any I/O error occur.
@ -103,13 +60,39 @@ public class BaseSystem
return log; return log;
} }
public String getDeviceInfo()
{
if (context == null) return "null context\n";
WindowManager wm =
(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
return
String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME) +
String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE) +
String.format("OS Version: %s (%s)\n",
System.getProperty("os.version"), Build.VERSION.INCREMENTAL) +
String.format("OS API Level: %s\n", Build.VERSION.SDK) +
String.format("Device: %s\n", Build.DEVICE) +
String.format("Model (Product): %s (%s)\n", Build.MODEL,
Build.PRODUCT) +
String.format("Manufacturer: %s\n", Build.MANUFACTURER) +
String.format("Other tags: %s\n", Build.TAGS) +
String.format("Screen Width: %s\n",
wm.getDefaultDisplay().getWidth()) +
String.format("Screen Height: %s\n",
wm.getDefaultDisplay().getHeight()) +
String.format("External storage state: %s\n\n",
Environment.getExternalStorageState());
}
public String getLogcat() throws IOException public String getLogcat() throws IOException
{ {
int maxLineCount = 250; int maxLineCount = 250;
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
String[] command = new String[]{ "logcat", "-d" }; String[] command = new String[]{ "logcat", "-d" };
Process process = Runtime.getRuntime().exec(command); java.lang.Process process = Runtime.getRuntime().exec(command);
InputStreamReader in = new InputStreamReader(process.getInputStream()); InputStreamReader in = new InputStreamReader(process.getInputStream());
BufferedReader bufferedReader = new BufferedReader(in); BufferedReader bufferedReader = new BufferedReader(in);
@ -132,29 +115,41 @@ public class BaseSystem
return builder.toString(); return builder.toString();
} }
private String getDeviceInfo() /**
* Captures a bug report and saves it to a file in the SD card.
* <p>
* The contents of the file are generated by the method {@link
* #getBugReport()}. The file is saved in the apps's external private
* storage.
*
* @return the generated file.
* @throws IOException when I/O errors occur.
*/
@NonNull
public void dumpBugReportToFile()
{ {
if (context == null) return "null context\n"; try
{
WindowManager wm = String date =
(WindowManager) context.getSystemService(Context.WINDOW_SERVICE); new SimpleDateFormat("yyyy-MM-dd HHmmss", Locale.US).format(
new Date());
return if (context == null) throw new IllegalStateException();
String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME) +
String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE) + File dir = new AndroidDirFinder(context).getFilesDir("Logs");
String.format("OS Version: %s (%s)\n", if (dir == null)
System.getProperty("os.version"), Build.VERSION.INCREMENTAL) + throw new IOException("log dir should not be null");
String.format("OS API Level: %s\n", Build.VERSION.SDK) +
String.format("Device: %s\n", Build.DEVICE) + File logFile =
String.format("Model (Product): %s (%s)\n", Build.MODEL, new File(String.format("%s/Log %s.txt", dir.getPath(), date));
Build.PRODUCT) + FileWriter output = new FileWriter(logFile);
String.format("Manufacturer: %s\n", Build.MANUFACTURER) + output.write(getBugReport());
String.format("Other tags: %s\n", Build.TAGS) + output.close();
String.format("Screen Width: %s\n", }
wm.getDefaultDisplay().getWidth()) + catch (IOException e)
String.format("Screen Height: %s\n", {
wm.getDefaultDisplay().getHeight()) + e.printStackTrace();
String.format("External storage state: %s\n\n", }
Environment.getExternalStorageState());
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com> * Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
* *
* This file is part of Loop Habit Tracker. * This file is part of Loop Habit Tracker.
* *
@ -17,38 +17,42 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits; package org.isoron.androidbase;
import android.content.*;
import android.support.annotation.*; import android.support.annotation.*;
import android.support.v4.content.*;
import android.util.*; import android.util.*;
import org.isoron.uhabits.models.*; import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.utils.*;
import java.text.*; import java.io.*;
import java.util.*;
import javax.inject.*; import javax.inject.*;
@AppScope public class AndroidDirFinder
public class HabitLogger
{ {
@NonNull
private Context context;
@Inject @Inject
public HabitLogger() public AndroidDirFinder(@NonNull @AppContext Context context)
{ {
this.context = context;
} }
public void logReminderScheduled(@NonNull Habit habit, @Nullable
@NonNull Long reminderTime) public File getFilesDir(@Nullable String relativePath)
{ {
int min = Math.min(3, habit.getName().length()); File externalFilesDirs[] =
String name = habit.getName().substring(0, min); ContextCompat.getExternalFilesDirs(context, null);
if (externalFilesDirs == null)
DateFormat df = DateFormats.getBackupDateFormat(); {
String time = df.format(new Date(reminderTime)); Log.e("BaseSystem",
"getFilesDir: getExternalFilesDirs returned null");
Log.i("ReminderHelper", return null;
String.format("Setting alarm (%s): %s", time, name)); }
return FileUtils.getDir(externalFilesDirs, relativePath);
} }
} }

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits; package org.isoron.androidbase;
import java.lang.annotation.*; import java.lang.annotation.*;

@ -17,18 +17,18 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits; package org.isoron.androidbase;
import android.content.*; import android.content.*;
import dagger.*; import dagger.*;
@Module @Module
public class AppModule public class AppContextModule
{ {
private final Context context; private final Context context;
public AppModule(@AppContext Context context) public AppContextModule(@AppContext Context context)
{ {
this.context = context; this.context = context;
} }

@ -0,0 +1,67 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.androidbase;
import android.support.annotation.*;
import org.isoron.androidbase.activities.*;
public class BaseExceptionHandler implements Thread.UncaughtExceptionHandler
{
@Nullable
private Thread.UncaughtExceptionHandler originalHandler;
@NonNull
private BaseActivity activity;
public BaseExceptionHandler(@NonNull BaseActivity activity)
{
this.activity = activity;
originalHandler = Thread.getDefaultUncaughtExceptionHandler();
}
@Override
public void uncaughtException(@Nullable Thread thread,
@Nullable Throwable ex)
{
if (ex == null) return;
try
{
ex.printStackTrace();
new AndroidBugReporter(activity).dumpBugReportToFile();
}
catch (Exception e)
{
e.printStackTrace();
}
// if (ex.getCause() instanceof InconsistentDatabaseException)
// {
// HabitsApplication app = (HabitsApplication) activity.getApplication();
// HabitList habits = app.getComponent().getHabitList();
// habits.repair();
// System.exit(0);
// }
if (originalHandler != null)
originalHandler.uncaughtException(thread, ex);
}
}

@ -0,0 +1,71 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.androidbase;
import android.content.*;
import android.support.annotation.*;
import org.isoron.androidbase.*;
import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.*;
import javax.inject.*;
import javax.net.ssl.*;
public class SSLContextProvider
{
private Context context;
@Inject
public SSLContextProvider(@NonNull @AppContext Context context)
{
this.context = context;
}
public SSLContext getCACertSSLContext()
{
try
{
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = context.getAssets().open("cacert.pem");
Certificate ca = cf.generateCertificate(caInput);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("ca", ca);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
return ctx;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
}

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.activities; package org.isoron.androidbase.activities;
import java.lang.annotation.*; import java.lang.annotation.*;

@ -17,20 +17,26 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits; package org.isoron.androidbase.activities;
import android.content.*;
import org.isoron.uhabits.models.sqlite.*;
import org.isoron.uhabits.tasks.*;
import dagger.*; import dagger.*;
@AppScope @Module
@Component(modules = { public class ActivityContextModule
AppModule.class, SingleThreadTaskRunner.class, SQLModelFactory.class
})
public interface AndroidTestComponent extends AppComponent
{ {
private Context context;
public ActivityContextModule(Context context)
{
this.context = context;
}
@Provides
@ActivityContext
public Context getContext()
{
return context;
}
} }

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.activities; package org.isoron.androidbase.activities;
import javax.inject.*; import javax.inject.*;

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.activities; package org.isoron.androidbase.activities;
import android.content.*; import android.content.*;
import android.os.*; import android.os.*;
@ -25,12 +25,10 @@ import android.support.annotation.*;
import android.support.v7.app.*; import android.support.v7.app.*;
import android.view.*; import android.view.*;
import org.isoron.uhabits.*; import org.isoron.androidbase.*;
import org.isoron.uhabits.activities.habits.list.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.models.sqlite.*;
import static android.R.anim.*; import static android.R.anim.fade_in;
import static android.R.anim.fade_out;
/** /**
* Base class for all activities in the application. * Base class for all activities in the application.
@ -41,28 +39,19 @@ import static android.R.anim.*;
* {@link BaseScreen}. * {@link BaseScreen}.
* <p> * <p>
* A BaseActivity also installs an {@link java.lang.Thread.UncaughtExceptionHandler} * A BaseActivity also installs an {@link java.lang.Thread.UncaughtExceptionHandler}
* to the main thread that logs the exception to the disk before the application * to the main thread. By default, this handler is an instance of
* crashes. * BaseExceptionHandler, which logs the exception to the disk before the application
* crashes. To the default handler, you should override the method
* getExceptionHandler.
*/ */
abstract public class BaseActivity extends AppCompatActivity abstract public class BaseActivity extends AppCompatActivity
implements Thread.UncaughtExceptionHandler
{ {
@Nullable @Nullable
private BaseMenu baseMenu; private BaseMenu baseMenu;
@Nullable
private Thread.UncaughtExceptionHandler androidExceptionHandler;
@Nullable @Nullable
private BaseScreen screen; private BaseScreen screen;
private ActivityComponent component;
public ActivityComponent getComponent()
{
return component;
}
@Override @Override
public boolean onCreateOptionsMenu(@Nullable Menu menu) public boolean onCreateOptionsMenu(@Nullable Menu menu)
{ {
@ -80,13 +69,13 @@ abstract public class BaseActivity extends AppCompatActivity
return baseMenu.onItemSelected(item); return baseMenu.onItemSelected(item);
} }
public void restartWithFade() public void restartWithFade(Class<?> cls)
{ {
new Handler().postDelayed(() -> { new Handler().postDelayed(() ->
Intent intent = new Intent(this, ListHabitsActivity.class); {
finish(); finish();
overridePendingTransition(fade_in, fade_out); overridePendingTransition(fade_in, fade_out);
startActivity(intent); startActivity(new Intent(this, cls));
}, 500); // HACK: Let the menu disappear first }, 500); // HACK: Let the menu disappear first
} }
@ -111,35 +100,6 @@ abstract public class BaseActivity extends AppCompatActivity
dialog.show(); dialog.show();
} }
@Override
public void uncaughtException(@Nullable Thread thread,
@Nullable Throwable ex)
{
if (ex == null) return;
try
{
ex.printStackTrace();
new BaseSystem(this).dumpBugReportToFile();
}
catch (Exception e)
{
// ignored
}
if (ex.getCause() instanceof InconsistentDatabaseException)
{
HabitsApplication app = (HabitsApplication) getApplication();
HabitList habits = app.getComponent().getHabitList();
habits.repair();
System.exit(0);
}
if (androidExceptionHandler != null)
androidExceptionHandler.uncaughtException(thread, ex);
else System.exit(1);
}
@Override @Override
protected void onActivityResult(int request, int result, Intent data) protected void onActivityResult(int request, int result, Intent data)
{ {
@ -151,18 +111,18 @@ abstract public class BaseActivity extends AppCompatActivity
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
{ {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Thread.setDefaultUncaughtExceptionHandler(getExceptionHandler());
}
androidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); protected Thread.UncaughtExceptionHandler getExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler(this); {
return new BaseExceptionHandler(this);
HabitsApplication app = (HabitsApplication) getApplicationContext(); }
component = DaggerActivityComponent
.builder()
.activityModule(new ActivityModule(this))
.appComponent(app.getComponent())
.build();
component.getThemeSwitcher().apply(); @Override
protected void onResume()
{
super.onResume();
if(screen != null) screen.reattachDialogs();
} }
} }

@ -17,31 +17,22 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.activities; package org.isoron.androidbase.activities;
import android.content.*;
import dagger.*; import dagger.*;
@Module @Module
public class ActivityModule public class BaseActivityModule
{ {
private BaseActivity activity; private BaseActivity activity;
public ActivityModule(BaseActivity activity) public BaseActivityModule(BaseActivity activity)
{ {
this.activity = activity; this.activity = activity;
} }
@Provides @Provides
public BaseActivity getActivity() public BaseActivity getBaseActivity()
{
return activity;
}
@Provides
@ActivityContext
public Context getContext()
{ {
return activity; return activity;
} }

@ -17,13 +17,11 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.activities; package org.isoron.androidbase.activities;
import android.support.annotation.*; import android.support.annotation.*;
import android.view.*; import android.view.*;
import javax.annotation.*;
/** /**
* Base class for all the menus in the application. * Base class for all the menus in the application.
* <p> * <p>
@ -41,6 +39,12 @@ public abstract class BaseMenu
this.activity = activity; this.activity = activity;
} }
@NonNull
public BaseActivity getActivity()
{
return activity;
}
/** /**
* Declare that the menu has changed, and should be recreated. * Declare that the menu has changed, and should be recreated.
*/ */
@ -64,14 +68,13 @@ public abstract class BaseMenu
/** /**
* Called when the menu is first displayed. * Called when the menu is first displayed.
* <p> * <p>
* This method cannot be overridden. The application should override the * This method should not be overridden. The application should override
* methods onCreate(Menu) and getMenuResourceId instead. * the methods onCreate(Menu) and getMenuResourceId instead.
* *
* @param inflater a menu inflater, for creating the menu * @param inflater a menu inflater, for creating the menu
* @param menu the menu that is being created. * @param menu the menu that is being created.
*/ */
public final void onCreate(@NonNull MenuInflater inflater, public void onCreate(@NonNull MenuInflater inflater, @NonNull Menu menu)
@NonNull Menu menu)
{ {
menu.clear(); menu.clear();
inflater.inflate(getMenuResourceId(), menu); inflater.inflate(getMenuResourceId(), menu);
@ -94,6 +97,6 @@ public abstract class BaseMenu
* *
* @return id of the menu resource. * @return id of the menu resource.
*/ */
@Resource @MenuRes
protected abstract int getMenuResourceId(); protected abstract int getMenuResourceId();
} }

@ -17,20 +17,19 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.activities; package org.isoron.androidbase.activities;
import android.content.*; import android.content.*;
import android.support.annotation.*; import android.support.annotation.*;
import android.support.v4.content.res.*;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.*; import android.view.*;
import android.widget.*; import android.widget.*;
import org.isoron.uhabits.*; import org.isoron.androidbase.*;
import org.isoron.uhabits.utils.*; import org.isoron.androidbase.utils.*;
import static android.os.Build.VERSION.*; import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.*; import static android.os.Build.VERSION_CODES.LOLLIPOP;
/** /**
* Base class for all root views in the application. * Base class for all root views in the application.
@ -42,36 +41,42 @@ import static android.os.Build.VERSION_CODES.*;
*/ */
public abstract class BaseRootView extends FrameLayout public abstract class BaseRootView extends FrameLayout
{ {
@NonNull
private final Context context; private final Context context;
private final BaseActivity activity; protected boolean shouldDisplayHomeAsUp = false;
private final ThemeSwitcher themeSwitcher; @Nullable
private BaseScreen screen;
public BaseRootView(Context context) public BaseRootView(@NonNull Context context)
{ {
super(context); super(context);
this.context = context; this.context = context;
activity = (BaseActivity) context;
themeSwitcher = activity.getComponent().getThemeSwitcher();
} }
public boolean getDisplayHomeAsUp() public boolean getDisplayHomeAsUp()
{ {
return false; return shouldDisplayHomeAsUp;
}
public void setDisplayHomeAsUp(boolean b)
{
shouldDisplayHomeAsUp = b;
} }
@NonNull @NonNull
public abstract Toolbar getToolbar(); public Toolbar getToolbar()
{
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
if (toolbar == null) throw new RuntimeException(
"Your BaseRootView should have a " +
"toolbar with id R.id.toolbar");
return toolbar;
}
public int getToolbarColor() public int getToolbarColor()
{ {
if (SDK_INT < LOLLIPOP && !themeSwitcher.isNightMode())
{
return ResourcesCompat.getColor(context.getResources(),
R.color.grey_900, context.getTheme());
}
StyledResources res = new StyledResources(context); StyledResources res = new StyledResources(context);
return res.getColor(R.attr.colorPrimary); return res.getColor(R.attr.colorPrimary);
} }
@ -86,7 +91,18 @@ public abstract class BaseRootView extends FrameLayout
if (view != null) view.setVisibility(GONE); if (view != null) view.setVisibility(GONE);
view = findViewById(R.id.headerShadow); view = findViewById(R.id.headerShadow);
if(view != null) view.setVisibility(GONE); if (view != null) view.setVisibility(GONE);
} }
} }
public void onAttachedToScreen(BaseScreen screen)
{
this.screen = screen;
}
@Nullable
public BaseScreen getScreen()
{
return screen;
}
} }

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.activities; package org.isoron.androidbase.activities;
import android.content.*; import android.content.*;
import android.graphics.*; import android.graphics.*;
@ -33,14 +33,14 @@ import android.support.v7.widget.Toolbar;
import android.view.*; import android.view.*;
import android.widget.*; import android.widget.*;
import org.isoron.uhabits.*; import org.isoron.androidbase.*;
import org.isoron.uhabits.utils.*; import org.isoron.androidbase.utils.*;
import java.io.*; import java.io.*;
import static android.os.Build.VERSION.*; import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.*; import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.support.v4.content.FileProvider.*; import static android.support.v4.content.FileProvider.getUriForFile;
/** /**
* Base class for all screens in the application. * Base class for all screens in the application.
@ -51,8 +51,6 @@ import static android.support.v4.content.FileProvider.*;
*/ */
public class BaseScreen public class BaseScreen
{ {
public static final int REQUEST_CREATE_DOCUMENT = 1;
protected BaseActivity activity; protected BaseActivity activity;
@Nullable @Nullable
@ -61,13 +59,28 @@ public class BaseScreen
@Nullable @Nullable
private BaseSelectionMenu selectionMenu; private BaseSelectionMenu selectionMenu;
private Snackbar snackbar; protected Snackbar snackbar;
public BaseScreen(@NonNull BaseActivity activity) public BaseScreen(@NonNull BaseActivity activity)
{ {
this.activity = activity; this.activity = activity;
} }
@Deprecated
public static int getDefaultActionBarColor(Context context)
{
if (SDK_INT < LOLLIPOP)
{
return ResourcesCompat.getColor(context.getResources(),
R.color.grey_900, context.getTheme());
}
else
{
StyledResources res = new StyledResources(context);
return res.getColor(R.attr.colorPrimary);
}
}
@Deprecated @Deprecated
public static void setupActionBarColor(@NonNull AppCompatActivity activity, public static void setupActionBarColor(@NonNull AppCompatActivity activity,
int color) int color)
@ -83,7 +96,6 @@ public class BaseScreen
actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setDisplayHomeAsUpEnabled(true);
ColorDrawable drawable = new ColorDrawable(color); ColorDrawable drawable = new ColorDrawable(color);
actionBar.setBackgroundDrawable(drawable); actionBar.setBackgroundDrawable(drawable);
@ -102,21 +114,6 @@ public class BaseScreen
} }
} }
@Deprecated
public static int getDefaultActionBarColor(Context context)
{
if (SDK_INT < LOLLIPOP)
{
return ResourcesCompat.getColor(context.getResources(),
R.color.grey_900, context.getTheme());
}
else
{
StyledResources res = new StyledResources(context);
return res.getColor(R.attr.colorPrimary);
}
}
/** /**
* Notifies the screen that its contents should be updated. * Notifies the screen that its contents should be updated.
*/ */
@ -130,7 +127,8 @@ public class BaseScreen
{ {
if (rootView == null) return; if (rootView == null) return;
activity.runOnUiThread(() -> { activity.runOnUiThread(() ->
{
Toolbar toolbar = rootView.getToolbar(); Toolbar toolbar = rootView.getToolbar();
activity.setSupportActionBar(toolbar); activity.setSupportActionBar(toolbar);
ActionBar actionBar = activity.getSupportActionBar(); ActionBar actionBar = activity.getSupportActionBar();
@ -159,6 +157,15 @@ public class BaseScreen
{ {
} }
/**
* Called after activity has been recreated, and the dialogs should be
* reattached to their controllers.
*/
public void reattachDialogs()
{
}
/** /**
* Sets the menu to be shown by this screen. * Sets the menu to be shown by this screen.
* <p> * <p>
@ -182,7 +189,7 @@ public class BaseScreen
this.rootView = rootView; this.rootView = rootView;
activity.setContentView(rootView); activity.setContentView(rootView);
if (rootView == null) return; if (rootView == null) return;
rootView.onAttachedToScreen(this);
invalidateToolbar(); invalidateToolbar();
} }

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.activities; package org.isoron.androidbase.activities;
import android.support.annotation.*; import android.support.annotation.*;
import android.support.v7.view.ActionMode; import android.support.v7.view.ActionMode;
@ -59,16 +59,16 @@ public abstract class BaseSelectionMenu
/** /**
* Called when the menu is first displayed. * Called when the menu is first displayed.
* <p> * <p>
* This method cannot be overridden. The application should override the * This method should not be overridden. The application should override
* methods onCreate(Menu) and getMenuResourceId instead. * the methods onCreate(Menu) and getMenuResourceId instead.
* *
* @param inflater a menu inflater, for creating the menu * @param inflater a menu inflater, for creating the menu
* @param mode the action mode associated with this menu. * @param mode the action mode associated with this menu.
* @param menu the menu that is being created. * @param menu the menu that is being created.
*/ */
public final void onCreate(@NonNull MenuInflater inflater, public void onCreate(@NonNull MenuInflater inflater,
@NonNull ActionMode mode, @NonNull ActionMode mode,
@NonNull Menu menu) @NonNull Menu menu)
{ {
this.actionMode = mode; this.actionMode = mode;
inflater.inflate(getResourceId(), menu); inflater.inflate(getResourceId(), menu);

@ -17,80 +17,12 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.utils; package org.isoron.androidbase.utils;
import android.content.*;
import android.graphics.*; import android.graphics.*;
import android.util.*;
public abstract class ColorUtils public abstract class ColorUtils
{ {
public static String CSV_PALETTE[] = {
"#D32F2F", // 0 red
"#E64A19", // 1 orange
"#F9A825", // 2 yellow
"#AFB42B", // 3 light green
"#388E3C", // 4 dark green
"#00897B", // 5 teal
"#00ACC1", // 6 cyan
"#039BE5", // 7 blue
"#5E35B1", // 8 deep purple
"#8E24AA", // 9 purple
"#D81B60", // 10 pink
"#303030", // 11 dark grey
"#aaaaaa" // 12 light grey
};
public static int colorToPaletteIndex(Context context, int color)
{
StyledResources res = new StyledResources(context);
int[] palette = res.getPalette();
for (int k = 0; k < palette.length; k++)
if (palette[k] == color) return k;
return -1;
}
public static int getAndroidTestColor(int index)
{
int palette[] = {
Color.parseColor("#D32F2F"), // 0 red
Color.parseColor("#E64A19"), // 1 orange
Color.parseColor("#F9A825"), // 2 yellow
Color.parseColor("#AFB42B"), // 3 light green
Color.parseColor("#388E3C"), // 4 dark green
Color.parseColor("#00897B"), // 5 teal
Color.parseColor("#00ACC1"), // 6 cyan
Color.parseColor("#039BE5"), // 7 blue
Color.parseColor("#5E35B1"), // 8 deep purple
Color.parseColor("#8E24AA"), // 9 purple
Color.parseColor("#D81B60"), // 10 pink
Color.parseColor("#303030"), // 11 dark grey
Color.parseColor("#aaaaaa") // 12 light grey
};
return palette[index];
}
public static int getColor(Context context, int paletteColor)
{
if (context == null)
throw new IllegalArgumentException("Context is null");
StyledResources res = new StyledResources(context);
int palette[] = res.getPalette();
if (paletteColor < 0 || paletteColor >= palette.length)
{
Log.w("ColorHelper",
String.format("Invalid color: %d. Returning default.",
paletteColor));
paletteColor = 0;
}
return palette[paletteColor];
}
public static int mixColors(int color1, int color2, float amount) public static int mixColors(int color1, int color2, float amount)
{ {
final byte ALPHA_CHANNEL = 24; final byte ALPHA_CHANNEL = 24;

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com> * Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
* *
* This file is part of Loop Habit Tracker. * This file is part of Loop Habit Tracker.
* *
@ -17,16 +17,12 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.utils; package org.isoron.androidbase.utils;
import android.content.*;
import android.os.*; import android.os.*;
import android.support.annotation.*; import android.support.annotation.*;
import android.support.v4.content.*;
import android.util.*; import android.util.*;
import org.isoron.uhabits.*;
import java.io.*; import java.io.*;
public abstract class FileUtils public abstract class FileUtils
@ -54,8 +50,8 @@ public abstract class FileUtils
} }
@Nullable @Nullable
private static File getDir(@NonNull File potentialParentDirs[], public static File getDir(@NonNull File potentialParentDirs[],
@Nullable String relativePath) @Nullable String relativePath)
{ {
if (relativePath == null) relativePath = ""; if (relativePath == null) relativePath = "";
@ -69,7 +65,7 @@ public abstract class FileUtils
if (chosenDir == null) if (chosenDir == null)
{ {
Log.e("DatabaseHelper", Log.e("FileUtils",
"getDir: all potential parents are null or non-writable"); "getDir: all potential parents are null or non-writable");
return null; return null;
} }
@ -78,7 +74,7 @@ public abstract class FileUtils
String.format("%s/%s/", chosenDir.getAbsolutePath(), relativePath)); String.format("%s/%s/", chosenDir.getAbsolutePath(), relativePath));
if (!dir.exists() && !dir.mkdirs()) if (!dir.exists() && !dir.mkdirs())
{ {
Log.e("DatabaseHelper", Log.e("FileUtils",
"getDir: chosen dir does not exist and cannot be created"); "getDir: chosen dir does not exist and cannot be created");
return null; return null;
} }
@ -86,22 +82,6 @@ public abstract class FileUtils
return dir; return dir;
} }
@Nullable
public static File getFilesDir(@NonNull Context context, @Nullable String relativePath)
{
File externalFilesDirs[] =
ContextCompat.getExternalFilesDirs(context, null);
if (externalFilesDirs == null)
{
Log.e("DatabaseHelper",
"getFilesDir: getExternalFilesDirs returned null");
return null;
}
return getDir(externalFilesDirs, relativePath);
}
@Nullable @Nullable
public static File getSDCardDir(@Nullable String relativePath) public static File getSDCardDir(@Nullable String relativePath)
{ {

@ -17,29 +17,42 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.utils; package org.isoron.androidbase.utils;
import android.content.*; import android.content.*;
import android.content.res.*; import android.content.res.*;
import android.graphics.*; import android.graphics.*;
import android.support.annotation.*;
import android.support.v4.view.*; import android.support.v4.view.*;
import android.util.*; import android.util.*;
import android.view.*; import android.view.*;
import android.widget.*;
public abstract class InterfaceUtils public abstract class InterfaceUtils
{ {
private static Typeface fontAwesome; private static Typeface fontAwesome;
@Nullable
private static Float fixedResolution = null;
public static void setFixedResolution(@NonNull Float f)
{
fixedResolution = f;
}
public static Typeface getFontAwesome(Context context) public static Typeface getFontAwesome(Context context)
{ {
if(fontAwesome == null) if(fontAwesome == null) fontAwesome =
fontAwesome = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf"); Typeface.createFromAsset(context.getAssets(),
"fontawesome-webfont.ttf");
return fontAwesome; return fontAwesome;
} }
public static float dpToPixels(Context context, float dp) public static float dpToPixels(Context context, float dp)
{ {
if(fixedResolution != null) return dp * fixedResolution;
Resources resources = context.getResources(); Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics(); DisplayMetrics metrics = resources.getDisplayMetrics();
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics); return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics);
@ -47,11 +60,40 @@ public abstract class InterfaceUtils
public static float spToPixels(Context context, float sp) public static float spToPixels(Context context, float sp)
{ {
if(fixedResolution != null) return sp * fixedResolution;
Resources resources = context.getResources(); Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics(); DisplayMetrics metrics = resources.getDisplayMetrics();
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, metrics); return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, metrics);
} }
public static float getDimension(Context context, int id)
{
float dim = context.getResources().getDimension(id);
if (fixedResolution == null) return dim;
else
{
DisplayMetrics dm = context.getResources().getDisplayMetrics();
float actualDensity = dm.density;
return dim / actualDensity * fixedResolution;
}
}
public static void setupEditorAction(@NonNull ViewGroup parent,
@NonNull TextView.OnEditorActionListener listener)
{
for (int i = 0; i < parent.getChildCount(); i++)
{
View child = parent.getChildAt(i);
if (child instanceof ViewGroup)
setupEditorAction((ViewGroup) child, listener);
if (child instanceof TextView)
((TextView) child).setOnEditorActionListener(listener);
}
}
public static boolean isLayoutRtl(View view) public static boolean isLayoutRtl(View view)
{ {
return ViewCompat.getLayoutDirection(view) == return ViewCompat.getLayoutDirection(view) ==

@ -17,14 +17,14 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.utils; package org.isoron.androidbase.utils;
import android.content.*; import android.content.*;
import android.content.res.*; import android.content.res.*;
import android.graphics.drawable.*; import android.graphics.drawable.*;
import android.support.annotation.*; import android.support.annotation.*;
import org.isoron.uhabits.*; import org.isoron.androidbase.*;
public class StyledResources public class StyledResources
{ {
@ -51,6 +51,15 @@ public class StyledResources
return bool; return bool;
} }
public int getDimension(@AttrRes int attrId)
{
TypedArray ta = getTypedArray(attrId);
int dim = ta.getDimensionPixelSize(0, 0);
ta.recycle();
return dim;
}
public int getColor(@AttrRes int attrId) public int getColor(@AttrRes int attrId)
{ {
TypedArray ta = getTypedArray(attrId); TypedArray ta = getTypedArray(attrId);
@ -80,13 +89,13 @@ public class StyledResources
public int[] getPalette() public int[] getPalette()
{ {
int resourceId = getStyleResource(R.attr.palette); int resourceId = getResource(R.attr.palette);
if (resourceId < 0) throw new RuntimeException("resource not found"); if (resourceId < 0) throw new RuntimeException("resource not found");
return context.getResources().getIntArray(resourceId); return context.getResources().getIntArray(resourceId);
} }
int getStyleResource(@AttrRes int attrId) public int getResource(@AttrRes int attrId)
{ {
TypedArray ta = getTypedArray(attrId); TypedArray ta = getTypedArray(attrId);
int resourceId = ta.getResourceId(0, -1); int resourceId = ta.getResourceId(0, -1);

@ -0,0 +1,6 @@
<resources>
<item name="toolbar" type="id" />
<item name="toolbarShadow" type="id" />
<item name="headerShadow" type="id" />
<attr name="palette" format="reference"/>
</resources>

@ -1,97 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources> <resources>
<array name="lightPalette">
<item>@color/red_700</item>
<item>@color/deep_orange_700</item>
<item>@color/yellow_800</item>
<item>@color/lime_700</item>
<item>@color/green_700</item>
<item>@color/teal_600</item>
<item>@color/cyan_600</item>
<item>@color/light_blue_600</item>
<item>@color/deep_purple_600</item>
<item>@color/purple_600</item>
<item>@color/pink_600</item>
<item>@color/grey_800</item>
<item>@color/grey_500</item>
</array>
<array name="darkPalette">
<item>@color/red_200</item>
<item>@color/deep_orange_200</item>
<item>@color/yellow_200</item>
<item>@color/lime_200</item>
<item>@color/green_A200</item>
<item>@color/teal_200</item>
<item>@color/cyan_200</item>
<item>@color/light_blue_200</item>
<item>@color/deep_purple_200</item>
<item>@color/purple_200</item>
<item>@color/pink_200</item>
<item>@color/grey_100</item>
<item>@color/grey_500</item>
</array>
<array name="transparentWidgetPalette">
<item>@color/red_800</item>
<item>@color/deep_orange_800</item>
<item>@color/yellow_800</item>
<item>@color/lime_800</item>
<item>@color/green_700</item>
<item>@color/teal_700</item>
<item>@color/cyan_700</item>
<item>@color/light_blue_700</item>
<item>@color/deep_purple_700</item>
<item>@color/purple_700</item>
<item>@color/pink_700</item>
<item>@color/black_aa</item>
<item>@color/black_aa</item>
</array>
<!-- Time and Date picker -->
<color name="circle_background">#f2f2f2</color>
<color name="line_background">#cccccc</color>
<color name="ampm_text_color">#8c8c8c</color>
<color name="done_text_color_normal">#000000</color>
<color name="done_text_color_disabled">#cccccc</color>
<color name="numbers_text_color">#8c8c8c</color>
<color name="transparent">#00000000</color>
<color name="transparent_black">#7f000000</color>
<color name="blue">#33b5e5</color>
<color name="blue_focused">#c1e8f7</color>
<color name="neutral_pressed">#33999999</color>
<color name="darker_blue">#0099cc</color>
<color name="date_picker_text_normal">#ff999999</color>
<color name="calendar_header">#999999</color>
<color name="date_picker_view_animator">#f2f2f2</color>
<color name="calendar_selected_date_text">#ffd1d2d4</color>
<color name="done_text_color">#888888</color>
<color name="done_text_color_dark">#888888</color>
<!-- Colors for red theme -->
<color name="red">#ff3333</color>
<color name="red_focused">#853333</color>
<color name="light_gray">#404040</color>
<color name="dark_gray">#363636</color>
<color name="line_dark">#808080</color>
<!-- Material design color palette -->
<color name="red_50">#FFEBEE</color> <color name="red_50">#FFEBEE</color>
<color name="red_100">#FFCDD2</color> <color name="red_100">#FFCDD2</color>
<color name="red_200">#EF9A9A</color> <color name="red_200">#EF9A9A</color>

@ -0,0 +1 @@
/build

@ -0,0 +1,25 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion COMPILE_SDK_VERSION as Integer
buildToolsVersion BUILD_TOOLS_VERSION
defaultConfig {
minSdkVersion MIN_SDK_VERSION as Integer
targetSdkVersion TARGET_SDK_VERSION as Integer
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION"
}

@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /gemini-b/opt/android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

@ -0,0 +1,2 @@
<manifest package="com.android"
xmlns:android="http://schemas.android.com/apk/res/android"/>

@ -16,18 +16,15 @@
package com.android.colorpicker; package com.android.colorpicker;
import android.app.Activity; import android.app.*;
import android.app.Dialog; import android.os.*;
import android.os.Bundle;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDialogFragment; import android.support.v7.app.*;
import android.view.LayoutInflater; import android.view.*;
import android.view.View; import android.widget.*;
import android.widget.ProgressBar;
import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener; import com.android.*;
import com.android.colorpicker.ColorPickerSwatch.*;
import org.isoron.uhabits.R;
/** /**
* A dialog which takes in as input an array of palette and creates a palette allowing the user to * A dialog which takes in as input an array of palette and creates a palette allowing the user to

@ -16,18 +16,14 @@
package com.android.colorpicker; package com.android.colorpicker;
import org.isoron.uhabits.R; import android.content.*;
import android.content.res.*;
import android.content.Context; import android.util.*;
import android.content.res.Resources; import android.view.*;
import android.util.AttributeSet; import android.widget.*;
import android.view.View;
import android.view.ViewGroup; import com.android.*;
import android.widget.ImageView; import com.android.colorpicker.ColorPickerSwatch.*;
import android.widget.TableLayout;
import android.widget.TableRow;
import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
/** /**
* A color picker custom view which creates an grid of color squares. The number of squares per * A color picker custom view which creates an grid of color squares. The number of squares per

@ -16,14 +16,12 @@
package com.android.colorpicker; package com.android.colorpicker;
import org.isoron.uhabits.R; import android.content.*;
import android.graphics.drawable.*;
import android.view.*;
import android.widget.*;
import android.content.Context; import com.android.*;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
/** /**
* Creates a circular swatch of a specified color. Adds a checkmark if marked as checked. * Creates a circular swatch of a specified color. Adds a checkmark if marked as checked.

@ -16,36 +16,23 @@
package com.android.datetimepicker.date; package com.android.datetimepicker.date;
import java.text.SimpleDateFormat; import android.animation.*;
import java.util.Calendar; import android.app.*;
import java.util.HashSet; import android.content.res.*;
import java.util.Iterator; import android.os.*;
import java.util.Locale; import android.text.format.*;
import android.util.*;
import org.isoron.uhabits.R; import android.view.*;
import android.view.View.*;
import android.animation.ObjectAnimator; import android.view.animation.*;
import android.app.Activity; import android.widget.*;
import android.app.DialogFragment;
import android.content.res.Resources; import com.android.*;
import android.os.Bundle; import com.android.datetimepicker.*;
import android.text.format.DateUtils; import com.android.datetimepicker.date.MonthAdapter.*;
import android.util.Log;
import android.view.LayoutInflater; import java.text.*;
import android.view.View; import java.util.*;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.datetimepicker.HapticFeedbackController;
import com.android.datetimepicker.Utils;
import com.android.datetimepicker.date.MonthAdapter.CalendarDay;
/** /**
* Dialog allowing users to select a date. * Dialog allowing users to select a date.

@ -16,37 +16,25 @@
package com.android.datetimepicker.date; package com.android.datetimepicker.date;
import java.security.InvalidParameterException; import android.content.*;
import java.util.Calendar; import android.content.res.*;
import android.graphics.*;
import android.graphics.Paint.*;
import android.os.*;
import android.support.v4.view.*;
import android.support.v4.view.accessibility.*;
import android.support.v4.widget.*;
import android.text.format.*;
import android.view.*;
import android.view.accessibility.*;
import com.android.*;
import com.android.datetimepicker.*;
import com.android.datetimepicker.date.MonthAdapter.*;
import java.security.*;
import java.util.*;
import java.util.Formatter; import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import org.isoron.uhabits.R;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.widget.ExploreByTouchHelper;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.android.datetimepicker.Utils;
import com.android.datetimepicker.date.MonthAdapter.CalendarDay;
/** /**
* A calendar-like view displaying a specified month and the appropriate selectable day numbers * A calendar-like view displaying a specified month and the appropriate selectable day numbers

@ -16,16 +16,14 @@
package com.android.datetimepicker.date; package com.android.datetimepicker.date;
import org.isoron.uhabits.R; import android.content.*;
import android.content.res.*;
import android.graphics.*;
import android.graphics.Paint.*;
import android.util.*;
import android.widget.*;
import android.content.Context; import com.android.*;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.widget.TextView;
/** /**
* A text view which, when pressed or activated, displays a blue circle around the text. * A text view which, when pressed or activated, displays a blue circle around the text.

@ -16,24 +16,18 @@
package com.android.datetimepicker.date; package com.android.datetimepicker.date;
import java.util.ArrayList; import android.content.*;
import java.util.List; import android.content.res.*;
import android.graphics.drawable.*;
import org.isoron.uhabits.R; import android.view.*;
import android.view.accessibility.*;
import android.content.Context; import android.widget.*;
import android.content.res.Resources; import android.widget.AdapterView.*;
import android.graphics.drawable.StateListDrawable;
import android.view.View; import com.android.*;
import android.view.ViewGroup; import com.android.datetimepicker.date.DatePickerDialog.*;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView; import java.util.*;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.android.datetimepicker.date.DatePickerDialog.OnDateChangedListener;
/** /**
* Displays a selectable list of years. * Displays a selectable list of years.

@ -16,20 +16,17 @@
package com.android.datetimepicker.time; package com.android.datetimepicker.time;
import java.text.DateFormatSymbols; import android.content.*;
import android.content.res.*;
import android.graphics.*;
import android.graphics.Paint.*;
import android.util.*;
import android.view.*;
import org.isoron.uhabits.R; import com.android.*;
import com.android.datetimepicker.*;
import android.content.Context; import java.text.*;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Typeface;
import android.util.Log;
import android.view.View;
import com.android.datetimepicker.Utils;
/** /**
* Draw the two smaller AM and PM circles next to where the larger circle will be. * Draw the two smaller AM and PM circles next to where the larger circle will be.

@ -16,14 +16,14 @@
package com.android.datetimepicker.time; package com.android.datetimepicker.time;
import org.isoron.uhabits.R;
import android.content.*;
import android.content.Context; import android.content.res.*;
import android.content.res.Resources; import android.graphics.*;
import android.graphics.Canvas; import android.util.*;
import android.graphics.Paint; import android.view.*;
import android.util.Log;
import android.view.View; import com.android.*;
/** /**
* Draws a simple white circle on which the numbers will be drawn. * Draws a simple white circle on which the numbers will be drawn.

@ -16,30 +16,20 @@
package com.android.datetimepicker.time; package com.android.datetimepicker.time;
import org.isoron.uhabits.R; import android.animation.*;
import android.annotation.*;
import android.animation.AnimatorSet; import android.content.*;
import android.animation.ObjectAnimator; import android.content.res.*;
import android.annotation.SuppressLint; import android.os.*;
import android.content.Context; import android.text.format.*;
import android.content.res.Resources; import android.util.*;
import android.os.Bundle; import android.view.*;
import android.os.Handler; import android.view.View.*;
import android.text.format.DateUtils; import android.view.accessibility.*;
import android.text.format.Time; import android.widget.*;
import android.util.AttributeSet;
import android.util.Log; import com.android.*;
import android.view.MotionEvent; import com.android.datetimepicker.*;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import com.android.datetimepicker.HapticFeedbackController;
/** /**
* The primary layout to hold the circular picker, and the am/pm buttons. This view well measure * The primary layout to hold the circular picker, and the am/pm buttons. This view well measure

@ -16,21 +16,16 @@
package com.android.datetimepicker.time; package com.android.datetimepicker.time;
import org.isoron.uhabits.R; import android.animation.*;
import android.animation.ValueAnimator.*;
import android.animation.Keyframe; import android.content.*;
import android.animation.ObjectAnimator; import android.content.res.*;
import android.animation.PropertyValuesHolder; import android.graphics.*;
import android.animation.ValueAnimator; import android.util.*;
import android.animation.ValueAnimator.AnimatorUpdateListener; import android.view.*;
import android.content.Context;
import android.content.res.Resources; import com.android.*;
import android.graphics.Canvas; import com.android.datetimepicker.*;
import android.graphics.Paint;
import android.util.Log;
import android.view.View;
import com.android.datetimepicker.Utils;
/** /**
* View to show what number is selected. This will draw a blue circle over the number, with a blue * View to show what number is selected. This will draw a blue circle over the number, with a blue

@ -16,21 +16,16 @@
package com.android.datetimepicker.time; package com.android.datetimepicker.time;
import org.isoron.uhabits.R; import android.animation.*;
import android.animation.ValueAnimator.*;
import android.animation.Keyframe; import android.content.*;
import android.animation.ObjectAnimator; import android.content.res.*;
import android.animation.PropertyValuesHolder; import android.graphics.*;
import android.animation.ValueAnimator; import android.graphics.Paint.*;
import android.animation.ValueAnimator.AnimatorUpdateListener; import android.util.*;
import android.content.Context; import android.view.*;
import android.content.res.Resources;
import android.graphics.Canvas; import com.android.*;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Typeface;
import android.util.Log;
import android.view.View;
/** /**
* A view to show a series of numbers in a circular pattern. * A view to show a series of numbers in a circular pattern.

@ -16,37 +16,25 @@
package com.android.datetimepicker.time; package com.android.datetimepicker.time;
import java.text.DateFormatSymbols; import android.animation.*;
import java.util.ArrayList; import android.annotation.*;
import java.util.Locale; import android.app.ActionBar.*;
import org.isoron.uhabits.R;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.*; import android.app.*;
import android.app.ActionBar; import android.content.*;
import android.app.ActionBar.LayoutParams; import android.content.res.*;
import android.content.Context; import android.os.*;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.v7.app.*; import android.support.v7.app.*;
import android.util.Log; import android.util.*;
import android.view.KeyCharacterMap; import android.view.*;
import android.view.KeyEvent; import android.view.View.*;
import android.view.LayoutInflater; import android.widget.*;
import android.view.View;
import android.view.View.OnClickListener; import com.android.*;
import android.view.View.OnKeyListener; import com.android.datetimepicker.*;
import android.view.ViewGroup; import com.android.datetimepicker.time.RadialPickerLayout.*;
import android.view.Window;
import android.widget.RelativeLayout; import java.text.*;
import android.widget.TextView; import java.util.*;
import com.android.datetimepicker.HapticFeedbackController;
import com.android.datetimepicker.Utils;
import com.android.datetimepicker.time.RadialPickerLayout.OnValueSelectedListener;
/** /**
* Dialog to set a time. * Dialog to set a time.

@ -24,16 +24,36 @@
<dimen name="color_swatch_margins_large">8dip</dimen> <dimen name="color_swatch_margins_large">8dip</dimen>
<dimen name="color_swatch_margins_small">4dip</dimen> <dimen name="color_swatch_margins_small">4dip</dimen>
<item name="circle_radius_multiplier" format="float" type="string" translatable="false">0.82</item> <item name="circle_radius_multiplier" format="float" translatable="false" type="string">
<item name="circle_radius_multiplier_24HourMode" format="float" type="string" translatable="false">0.85</item> 0.82
<item name="selection_radius_multiplier" format="float" type="string" translatable="false">0.16</item> </item>
<item name="ampm_circle_radius_multiplier" format="float" type="string" translatable="false">0.19</item> <item name="circle_radius_multiplier_24HourMode" format="float" translatable="false" type="string">
<item name="numbers_radius_multiplier_normal" format="float" type="string" translatable="false">0.81</item> 0.85
<item name="numbers_radius_multiplier_inner" format="float" type="string" translatable="false">0.60</item> </item>
<item name="numbers_radius_multiplier_outer" format="float" type="string" translatable="false">0.83</item> <item name="selection_radius_multiplier" format="float" translatable="false" type="string">
<item name="text_size_multiplier_normal" format="float" type="string" translatable="false">0.17</item> 0.16
<item name="text_size_multiplier_inner" format="float" type="string" translatable="false">0.14</item> </item>
<item name="text_size_multiplier_outer" format="float" type="string" translatable="false">0.11</item> <item name="ampm_circle_radius_multiplier" format="float" translatable="false" type="string">
0.19
</item>
<item name="numbers_radius_multiplier_normal" format="float" translatable="false" type="string">
0.81
</item>
<item name="numbers_radius_multiplier_inner" format="float" translatable="false" type="string">
0.60
</item>
<item name="numbers_radius_multiplier_outer" format="float" translatable="false" type="string">
0.83
</item>
<item name="text_size_multiplier_normal" format="float" translatable="false" type="string">
0.17
</item>
<item name="text_size_multiplier_inner" format="float" translatable="false" type="string">
0.14
</item>
<item name="text_size_multiplier_outer" format="float" translatable="false" type="string">
0.11
</item>
<dimen name="time_label_size">60sp</dimen> <dimen name="time_label_size">60sp</dimen>
<dimen name="extra_time_label_margin">-30dp</dimen> <dimen name="extra_time_label_margin">-30dp</dimen>
@ -64,8 +84,8 @@
<dimen name="year_label_height">64dp</dimen> <dimen name="year_label_height">64dp</dimen>
<dimen name="year_label_text_size">22sp</dimen> <dimen name="year_label_text_size">22sp</dimen>
<string name="color_swatch_description" translatable="false">Color <xliff:g id="color_index" example="14">%1$d</xliff:g></string> <string name="color_swatch_description" translatable="false">Color <xliff:g example="14" id="color_index">%1$d</xliff:g></string>
<string name="color_swatch_description_selected" translatable="false">Color <xliff:g id="color_index" example="14">%1$d</xliff:g> selected</string> <string name="color_swatch_description_selected" translatable="false">Color <xliff:g example="14" id="color_index">%1$d</xliff:g> selected</string>
<!-- Date and time picker --> <!-- Date and time picker -->
<string name="hour_picker_description" translatable="false">Hours circular slider</string> <string name="hour_picker_description" translatable="false">Hours circular slider</string>
@ -74,11 +94,56 @@
<string name="year_picker_description" translatable="false">Year list</string> <string name="year_picker_description" translatable="false">Year list</string>
<string name="select_day" translatable="false">Select month and day</string> <string name="select_day" translatable="false">Select month and day</string>
<string name="select_year" translatable="false">Select year</string> <string name="select_year" translatable="false">Select year</string>
<string name="item_is_selected" translatable="false"><xliff:g id="item" example="2013">%1$s</xliff:g> selected</string> <string name="item_is_selected" translatable="false"><xliff:g example="2013" id="item">%1$s</xliff:g> selected</string>
<string name="deleted_key" translatable="false"><xliff:g id="key" example="4">%1$s</xliff:g> deleted</string> <string name="deleted_key" translatable="false"><xliff:g example="4" id="key">%1$s</xliff:g> deleted</string>
<string name="time_placeholder" translatable="false">--</string> <string name="time_placeholder" translatable="false">--</string>
<string name="time_separator" translatable="false">:</string> <string name="time_separator" translatable="false">:</string>
<string name="radial_numbers_typeface" translatable="false">sans-serif</string> <string name="radial_numbers_typeface" translatable="false">sans-serif</string>
<string name="sans_serif" translatable="false">sans-serif</string> <string name="sans_serif" translatable="false">sans-serif</string>
<string name="day_of_week_label_typeface" translatable="false">sans-serif</string> <string name="day_of_week_label_typeface" translatable="false">sans-serif</string>
<!-- Time and Date picker -->
<color name="circle_background">#f2f2f2</color>
<color name="line_background">#cccccc</color>
<color name="ampm_text_color">#8c8c8c</color>
<color name="done_text_color_normal">#000000</color>
<color name="done_text_color_disabled">#cccccc</color>
<color name="numbers_text_color">#8c8c8c</color>
<color name="transparent">#00000000</color>
<color name="transparent_black">#7f000000</color>
<color name="blue">#33b5e5</color>
<color name="blue_focused">#c1e8f7</color>
<color name="neutral_pressed">#33999999</color>
<color name="darker_blue">#0099cc</color>
<color name="date_picker_text_normal">#ff999999</color>
<color name="calendar_header">#999999</color>
<color name="date_picker_view_animator">#f2f2f2</color>
<color name="calendar_selected_date_text">#ffd1d2d4</color>
<color name="done_text_color">#888888</color>
<color name="done_text_color_dark">#888888</color>
<color name="white">#ffffff</color>
<color name="black">#000000</color>
<!-- Colors for red theme -->
<color name="red">#ff3333</color>
<color name="red_focused">#853333</color>
<color name="light_gray">#404040</color>
<color name="dark_gray">#363636</color>
<color name="line_dark">#808080</color>
<style name="time_label">
<item name="android:textSize">@dimen/time_label_size</item>
<item name="android:textColor">@color/numbers_text_color</item>
</style>
<style name="ampm_label">
<item name="android:textSize">@dimen/ampm_label_size</item>
<item name="android:textAllCaps">true</item>
<item name="android:textColor">@color/ampm_text_color</item>
<item name="android:textStyle">bold</item>
</style>
<style name="TimePickerDialog" parent="@style/Theme.AppCompat.Light.Dialog">
<item name="windowNoTitle">true</item>
</style>
</resources> </resources>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="select_hours"/>
<string name="select_minutes"/>
<string name="color_picker_default_title"/>
<string name="clear"/>
<string name="clear_label"/>
<string name="done_label"/>
</resources>

@ -1,3 +0,0 @@
-dontwarn java.beans.**
-dontwarn java.lang.**
-dontobfuscate

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

@ -1,5 +0,0 @@
#!/bin/bash
P=/sdcard/Android/data/org.isoron.uhabits/cache/Failed/
adb pull $P Failed/
adb shell rm -r $P

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 559 B

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save