mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 17:18:52 -06:00
Merge branch 'dev' into edit-redesign
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion COMPILE_SDK_VERSION as Integer
|
||||
@@ -28,4 +29,5 @@ dependencies {
|
||||
implementation "org.apache.commons:commons-lang3:3.5"
|
||||
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
|
||||
}
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.androidbase;
|
||||
|
||||
import android.content.*;
|
||||
import android.os.*;
|
||||
import android.view.*;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
public class AndroidBugReporter
|
||||
{
|
||||
private final Context context;
|
||||
|
||||
@Inject
|
||||
public AndroidBugReporter(@NonNull @AppContext Context context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures and returns a bug report. The bug report contains some device
|
||||
* information and the logcat.
|
||||
*
|
||||
* @return a String containing the bug report.
|
||||
* @throws IOException when any I/O error occur.
|
||||
*/
|
||||
@NonNull
|
||||
public String getBugReport() throws IOException
|
||||
{
|
||||
String logcat = getLogcat();
|
||||
String deviceInfo = getDeviceInfo();
|
||||
|
||||
String log = "---------- BUG REPORT BEGINS ----------\n";
|
||||
log += deviceInfo + "\n" + logcat;
|
||||
log += "---------- BUG REPORT ENDS ------------\n";
|
||||
|
||||
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
|
||||
{
|
||||
int maxLineCount = 250;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
String[] command = new String[]{ "logcat", "-d" };
|
||||
java.lang.Process process = Runtime.getRuntime().exec(command);
|
||||
|
||||
InputStreamReader in = new InputStreamReader(process.getInputStream());
|
||||
BufferedReader bufferedReader = new BufferedReader(in);
|
||||
|
||||
LinkedList<String> log = new LinkedList<>();
|
||||
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null)
|
||||
{
|
||||
log.addLast(line);
|
||||
if (log.size() > maxLineCount) log.removeFirst();
|
||||
}
|
||||
|
||||
for (String l : log)
|
||||
{
|
||||
builder.append(l);
|
||||
builder.append('\n');
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
String date =
|
||||
new SimpleDateFormat("yyyy-MM-dd HHmmss", Locale.US).format(
|
||||
new Date());
|
||||
|
||||
if (context == null) throw new IllegalStateException();
|
||||
|
||||
File dir = new AndroidDirFinder(context).getFilesDir("Logs");
|
||||
if (dir == null)
|
||||
throw new IOException("log dir should not be null");
|
||||
|
||||
File logFile =
|
||||
new File(String.format("%s/Log %s.txt", dir.getPath(), date));
|
||||
FileWriter output = new FileWriter(logFile);
|
||||
output.write(getBugReport());
|
||||
output.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.Context
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.view.WindowManager
|
||||
import java.io.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
open class AndroidBugReporter @Inject constructor(@AppContext private val context: Context) {
|
||||
|
||||
/**
|
||||
* Captures and returns a bug report. The bug report contains some device
|
||||
* information and the logcat.
|
||||
*
|
||||
* @return a String containing the bug report.
|
||||
* @throws IOException when any I/O error occur.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun getBugReport(): String {
|
||||
var log = "---------- BUG REPORT BEGINS ----------\n"
|
||||
log += "${getLogcat()}\n"
|
||||
log += "${getDeviceInfo()}\n"
|
||||
log += "---------- BUG REPORT ENDS ------------\n"
|
||||
return log
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun getLogcat(): String {
|
||||
val maxLineCount = 250
|
||||
val builder = StringBuilder()
|
||||
val process = Runtime.getRuntime().exec(arrayOf("logcat", "-d"))
|
||||
val inputReader = InputStreamReader(process.inputStream)
|
||||
val bufferedReader = BufferedReader(inputReader)
|
||||
val log = LinkedList<String>()
|
||||
var line: String?
|
||||
while (true) {
|
||||
line = bufferedReader.readLine()
|
||||
if (line == null) break;
|
||||
log.addLast(line)
|
||||
if (log.size > maxLineCount) log.removeFirst()
|
||||
}
|
||||
for (l in log) {
|
||||
builder.appendln(l)
|
||||
}
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures a bug report and saves it to a file in the SD card.
|
||||
*
|
||||
* The contents of the file are generated by the method [ ][.getBugReport]. The file is saved
|
||||
* in the apps's external private storage.
|
||||
*
|
||||
* @return the generated file.
|
||||
* @throws IOException when I/O errors occur.
|
||||
*/
|
||||
fun dumpBugReportToFile() {
|
||||
try {
|
||||
val date = SimpleDateFormat("yyyy-MM-dd HHmmss", Locale.US).format(Date())
|
||||
val dir = AndroidDirFinder(context).getFilesDir("Logs")
|
||||
?: throw IOException("log dir should not be null")
|
||||
val logFile = File(String.format("%s/Log %s.txt", dir.path, date))
|
||||
val output = FileWriter(logFile)
|
||||
output.write(getBugReport())
|
||||
output.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDeviceInfo(): String {
|
||||
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
return buildString {
|
||||
appendln("App Version Name: ${BuildConfig.VERSION_NAME}")
|
||||
appendln("App Version Code: ${BuildConfig.VERSION_CODE}")
|
||||
appendln("OS Version: ${System.getProperty("os.version")} (${Build.VERSION.INCREMENTAL})")
|
||||
appendln("OS API Level: ${Build.VERSION.SDK}")
|
||||
appendln("Device: ${Build.DEVICE}")
|
||||
appendln("Model (Product): ${Build.MODEL} (${Build.PRODUCT})")
|
||||
appendln("Manufacturer: ${Build.MANUFACTURER}")
|
||||
appendln("Other tags: ${Build.TAGS}")
|
||||
appendln("Screen Width: ${wm.defaultDisplay.width}")
|
||||
appendln("Screen Height: ${wm.defaultDisplay.height}")
|
||||
appendln("External storage state: ${Environment.getExternalStorageState()}")
|
||||
appendln()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.androidbase;
|
||||
|
||||
import android.content.*;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.*;
|
||||
import android.util.*;
|
||||
|
||||
import org.isoron.androidbase.utils.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
public class AndroidDirFinder
|
||||
{
|
||||
@NonNull
|
||||
private Context context;
|
||||
|
||||
@Inject
|
||||
public AndroidDirFinder(@NonNull @AppContext Context context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public File getFilesDir(@Nullable String relativePath)
|
||||
{
|
||||
File externalFilesDirs[] =
|
||||
ContextCompat.getExternalFilesDirs(context, null);
|
||||
if (externalFilesDirs == null)
|
||||
{
|
||||
Log.e("BaseSystem",
|
||||
"getFilesDir: getExternalFilesDirs returned null");
|
||||
return null;
|
||||
}
|
||||
|
||||
return FileUtils.getDir(externalFilesDirs, relativePath);
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,19 @@
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.uhabits.activities.about;
|
||||
package org.isoron.androidbase
|
||||
|
||||
import org.isoron.uhabits.core.ui.screens.about.*;
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.isoron.androidbase.utils.FileUtils
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
import dagger.*;
|
||||
|
||||
@Module
|
||||
public abstract class AboutModule
|
||||
{
|
||||
@Binds
|
||||
abstract AboutBehavior.Screen getScreen(AboutScreen screen);
|
||||
}
|
||||
class AndroidDirFinder @Inject constructor(@param:AppContext private val context: Context) {
|
||||
fun getFilesDir(relativePath: String?): File? {
|
||||
return FileUtils.getDir(
|
||||
ContextCompat.getExternalFilesDirs(context, null),
|
||||
relativePath
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -16,16 +16,14 @@
|
||||
* 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
|
||||
|
||||
package org.isoron.androidbase;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
import javax.inject.*;
|
||||
import java.lang.annotation.Documented
|
||||
import java.lang.annotation.Retention
|
||||
import java.lang.annotation.RetentionPolicy
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AppContext
|
||||
{
|
||||
}
|
||||
annotation class AppContext
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.androidbase;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
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,39 @@
|
||||
/*
|
||||
* 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 org.isoron.androidbase.activities.BaseActivity
|
||||
|
||||
class BaseExceptionHandler(private val activity: BaseActivity) : Thread.UncaughtExceptionHandler {
|
||||
|
||||
private val originalHandler: Thread.UncaughtExceptionHandler? =
|
||||
Thread.getDefaultUncaughtExceptionHandler()
|
||||
|
||||
override fun uncaughtException(thread: Thread?, ex: Throwable?) {
|
||||
if (ex == null) return
|
||||
if (thread == null) return
|
||||
try {
|
||||
ex.printStackTrace()
|
||||
AndroidBugReporter(activity).dumpBugReportToFile()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
originalHandler?.uncaughtException(thread, ex)
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.androidbase;
|
||||
|
||||
import android.content.*;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.androidbase
|
||||
|
||||
import android.content.Context
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.CertificateFactory
|
||||
import javax.inject.Inject
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
|
||||
class SSLContextProvider @Inject constructor(@param:AppContext private val context: Context) {
|
||||
fun getCACertSSLContext(): SSLContext {
|
||||
try {
|
||||
val cf = CertificateFactory.getInstance("X.509")
|
||||
val ca = cf.generateCertificate(context.assets.open("cacert.pem"))
|
||||
val ks = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
|
||||
load(null, null)
|
||||
setCertificateEntry("ca", ca)
|
||||
}
|
||||
val alg = TrustManagerFactory.getDefaultAlgorithm()
|
||||
val tmf = TrustManagerFactory.getInstance(alg).apply {
|
||||
init(ks)
|
||||
}
|
||||
return SSLContext.getInstance("TLS").apply {
|
||||
init(null, tmf.trustManagers, null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -878,7 +878,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
|
||||
// When the first digit is 2, the second digit may be 4-5.
|
||||
secondDigit = new Node(k4, k5);
|
||||
firstDigit.addChild(secondDigit);
|
||||
// We must now be followd by the last minute digit. E.g. 2:40, 2:53.
|
||||
// We must now be followed by the last minute digit. E.g. 2:40, 2:53.
|
||||
secondDigit.addChild(minuteSecondDigit);
|
||||
|
||||
// The first digit may be 3-9.
|
||||
|
||||
378
android/build.sh
378
android/build.sh
@@ -25,294 +25,256 @@ OUTPUTS_DIR=uhabits-android/build/outputs
|
||||
VERSION=$(cat gradle.properties | grep VERSION_NAME | sed -e 's/.*=//g;s/ //g')
|
||||
|
||||
if [ ! -f "${ANDROID_HOME}/platform-tools/adb" ]; then
|
||||
echo "Error: ANDROID_HOME is not set correctly"
|
||||
exit 1
|
||||
echo "Error: ANDROID_HOME is not set correctly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_error() {
|
||||
if [ ! -z "$TEAMCITY_VERSION" ]; then
|
||||
echo "###teamcity[progressMessage '$1']"
|
||||
else
|
||||
local COLOR='\033[1;31m'
|
||||
local NC='\033[0m'
|
||||
echo -e "$COLOR>>> $1 $NC"
|
||||
fi
|
||||
if [ ! -z "$TEAMCITY_VERSION" ]; then
|
||||
echo "###teamcity[progressMessage '$1']"
|
||||
else
|
||||
local COLOR='\033[1;31m'
|
||||
local NC='\033[0m'
|
||||
echo -e "$COLOR>>> $1 $NC"
|
||||
fi
|
||||
}
|
||||
|
||||
log_info() {
|
||||
if [ ! -z "$TEAMCITY_VERSION" ]; then
|
||||
echo "###teamcity[progressMessage '$1']"
|
||||
else
|
||||
local COLOR='\033[1;32m'
|
||||
local NC='\033[0m'
|
||||
echo -e "$COLOR>>> $1 $NC"
|
||||
fi
|
||||
if [ ! -z "$TEAMCITY_VERSION" ]; then
|
||||
echo "###teamcity[progressMessage '$1']"
|
||||
else
|
||||
local COLOR='\033[1;32m'
|
||||
local NC='\033[0m'
|
||||
echo -e "$COLOR>>> $1 $NC"
|
||||
fi
|
||||
}
|
||||
|
||||
fail() {
|
||||
if [ ! -z ${AVD_NAME} ]; then
|
||||
stop_emulator
|
||||
stop_gradle_daemon
|
||||
fi
|
||||
log_error "BUILD FAILED"
|
||||
exit 1
|
||||
log_error "BUILD FAILED"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ ! -z $RELEASE ]; then
|
||||
log_info "Reading secret env variables from ../.secret/env"
|
||||
source ../.secret/env || fail
|
||||
log_info "Reading secret env variables from ../.secret/env"
|
||||
source ../.secret/env || fail
|
||||
fi
|
||||
|
||||
|
||||
start_emulator() {
|
||||
log_info "Starting emulator ($AVD_NAME)"
|
||||
$EMULATOR -avd ${AVD_NAME} -port ${AVD_SERIAL} -no-audio -no-window &
|
||||
$ADB wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done; input keyevent 82'
|
||||
}
|
||||
|
||||
stop_emulator() {
|
||||
log_info "Stopping emulator"
|
||||
$ADB emu kill
|
||||
}
|
||||
|
||||
stop_gradle_daemon() {
|
||||
log_info "Stopping gradle daemon"
|
||||
$GRADLE --stop
|
||||
}
|
||||
|
||||
run_adb_as_root() {
|
||||
log_info "Running adb as root"
|
||||
$ADB root
|
||||
log_info "Running adb as root"
|
||||
$ADB root
|
||||
}
|
||||
|
||||
build_apk() {
|
||||
log_info "Removing old APKs..."
|
||||
rm -vf build/*.apk
|
||||
log_info "Removing old APKs..."
|
||||
rm -vf build/*.apk
|
||||
|
||||
if [ ! -z $RELEASE ]; then
|
||||
log_info "Building release APK"
|
||||
./gradlew assembleRelease
|
||||
cp -v uhabits-android/build/outputs/apk/release/uhabits-android-release.apk build/loop-$VERSION-release.apk
|
||||
fi
|
||||
if [ ! -z $RELEASE ]; then
|
||||
log_info "Building release APK"
|
||||
./gradlew assembleRelease
|
||||
cp -v uhabits-android/build/outputs/apk/release/uhabits-android-release.apk build/loop-$VERSION-release.apk
|
||||
fi
|
||||
|
||||
log_info "Building debug APK"
|
||||
./gradlew assembleDebug || fail
|
||||
cp -v uhabits-android/build/outputs/apk/debug/uhabits-android-debug.apk build/loop-$VERSION-debug.apk
|
||||
log_info "Building debug APK"
|
||||
./gradlew assembleDebug || fail
|
||||
cp -v uhabits-android/build/outputs/apk/debug/uhabits-android-debug.apk build/loop-$VERSION-debug.apk
|
||||
}
|
||||
|
||||
build_instrumentation_apk() {
|
||||
log_info "Building instrumentation APK"
|
||||
if [ ! -z $RELEASE ]; then
|
||||
$GRADLE assembleAndroidTest \
|
||||
-Pandroid.injected.signing.store.file=$LOOP_KEY_STORE \
|
||||
-Pandroid.injected.signing.store.password=$LOOP_STORE_PASSWORD \
|
||||
-Pandroid.injected.signing.key.alias=$LOOP_KEY_ALIAS \
|
||||
-Pandroid.injected.signing.key.password=$LOOP_KEY_PASSWORD || fail
|
||||
else
|
||||
$GRADLE assembleAndroidTest || fail
|
||||
fi
|
||||
}
|
||||
|
||||
clean_output_dir() {
|
||||
log_info "Cleaning output directory"
|
||||
rm -rf ${OUTPUTS_DIR}
|
||||
mkdir -p ${OUTPUTS_DIR}
|
||||
log_info "Building instrumentation APK"
|
||||
if [ ! -z $RELEASE ]; then
|
||||
$GRADLE assembleAndroidTest \
|
||||
-Pandroid.injected.signing.store.file=$LOOP_KEY_STORE \
|
||||
-Pandroid.injected.signing.store.password=$LOOP_STORE_PASSWORD \
|
||||
-Pandroid.injected.signing.key.alias=$LOOP_KEY_ALIAS \
|
||||
-Pandroid.injected.signing.key.password=$LOOP_KEY_PASSWORD || fail
|
||||
else
|
||||
$GRADLE assembleAndroidTest || fail
|
||||
fi
|
||||
}
|
||||
|
||||
uninstall_apk() {
|
||||
log_info "Uninstalling existing APK"
|
||||
$ADB uninstall ${PACKAGE_NAME}
|
||||
log_info "Uninstalling existing APK"
|
||||
$ADB uninstall ${PACKAGE_NAME}
|
||||
}
|
||||
|
||||
install_test_butler() {
|
||||
log_info "Installing Test Butler"
|
||||
$ADB uninstall com.linkedin.android.testbutler
|
||||
$ADB install tools/test-butler-app-2.0.2.apk
|
||||
log_info "Installing Test Butler"
|
||||
$ADB uninstall com.linkedin.android.testbutler
|
||||
$ADB install tools/test-butler-app-2.0.2.apk
|
||||
}
|
||||
|
||||
install_apk() {
|
||||
log_info "Installing APK"
|
||||
if [ ! -z $RELEASE ]; then
|
||||
$ADB install -r ${OUTPUTS_DIR}/apk/release/uhabits-android-release.apk || fail
|
||||
else
|
||||
$ADB install -t -r ${OUTPUTS_DIR}/apk/debug/uhabits-android-debug.apk || fail
|
||||
fi
|
||||
log_info "Installing APK"
|
||||
if [ ! -z $RELEASE ]; then
|
||||
$ADB install -r ${OUTPUTS_DIR}/apk/release/uhabits-android-release.apk || fail
|
||||
else
|
||||
$ADB install -t -r ${OUTPUTS_DIR}/apk/debug/uhabits-android-debug.apk || fail
|
||||
fi
|
||||
}
|
||||
|
||||
install_test_apk() {
|
||||
log_info "Uninstalling existing test APK"
|
||||
$ADB uninstall ${PACKAGE_NAME}.test
|
||||
log_info "Uninstalling existing test APK"
|
||||
$ADB uninstall ${PACKAGE_NAME}.test
|
||||
|
||||
log_info "Installing test APK"
|
||||
$ADB install -r ${OUTPUTS_DIR}/apk/androidTest/debug/uhabits-android-debug-androidTest.apk || fail
|
||||
log_info "Installing test APK"
|
||||
$ADB install -r ${OUTPUTS_DIR}/apk/androidTest/debug/uhabits-android-debug-androidTest.apk || fail
|
||||
}
|
||||
|
||||
run_instrumented_tests() {
|
||||
SIZE=$1
|
||||
log_info "Running instrumented tests"
|
||||
$ADB shell am instrument \
|
||||
-r -e coverage true -e size $SIZE \
|
||||
-w ${PACKAGE_NAME}.test/android.support.test.runner.AndroidJUnitRunner \
|
||||
| tee ${OUTPUTS_DIR}/instrument.txt
|
||||
SIZE=$1
|
||||
log_info "Running instrumented tests"
|
||||
$ADB shell am instrument \
|
||||
-r -e coverage true -e size $SIZE \
|
||||
-w ${PACKAGE_NAME}.test/androidx.test.runner.AndroidJUnitRunner \
|
||||
| tee ${OUTPUTS_DIR}/instrument.txt
|
||||
|
||||
if grep FAILURES $OUTPUTS_DIR/instrument.txt; then
|
||||
log_error "Some instrumented tests failed"
|
||||
fetch_images
|
||||
fetch_logcat
|
||||
exit 1
|
||||
fi
|
||||
if grep "\(INSTRUMENTATION_STATUS_CODE.*-1\|FAILURES\)" $OUTPUTS_DIR/instrument.txt; then
|
||||
log_error "Some instrumented tests failed"
|
||||
fetch_images
|
||||
fetch_logcat
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#mkdir -p ${OUTPUTS_DIR}/code-coverage/connected/
|
||||
#$ADB pull /data/user/0/${PACKAGE_NAME}/files/coverage.ec \
|
||||
# ${OUTPUTS_DIR}/code-coverage/connected/ \
|
||||
# || log_error "COVERAGE REPORT NOT AVAILABLE"
|
||||
#mkdir -p ${OUTPUTS_DIR}/code-coverage/connected/
|
||||
#$ADB pull /data/user/0/${PACKAGE_NAME}/files/coverage.ec \
|
||||
# ${OUTPUTS_DIR}/code-coverage/connected/ \
|
||||
# || log_error "COVERAGE REPORT NOT AVAILABLE"
|
||||
}
|
||||
|
||||
parse_instrumentation_results() {
|
||||
log_info "Parsing instrumented test results"
|
||||
java -jar tools/automator-log-converter-1.5.0.jar ${OUTPUTS_DIR}/instrument.txt || fail
|
||||
log_info "Parsing instrumented test results"
|
||||
java -jar tools/automator-log-converter-1.5.0.jar ${OUTPUTS_DIR}/instrument.txt || fail
|
||||
}
|
||||
|
||||
generate_coverage_badge() {
|
||||
log_info "Generating code coverage badge"
|
||||
CORE_REPORT=uhabits-core/build/reports/jacoco/test/jacocoTestReport.xml
|
||||
rm -f ${OUTPUTS_DIR}/coverage-badge.svg
|
||||
python3 tools/coverage-badge/badge.py -i $CORE_REPORT -o ${OUTPUTS_DIR}/coverage-badge
|
||||
log_info "Generating code coverage badge"
|
||||
CORE_REPORT=uhabits-core/build/reports/jacoco/test/jacocoTestReport.xml
|
||||
rm -f ${OUTPUTS_DIR}/coverage-badge.svg
|
||||
python3 tools/coverage-badge/badge.py -i $CORE_REPORT -o ${OUTPUTS_DIR}/coverage-badge
|
||||
}
|
||||
|
||||
fetch_logcat() {
|
||||
log_info "Fetching logcat"
|
||||
$ADB logcat -d > ${OUTPUTS_DIR}/logcat.txt
|
||||
log_info "Fetching logcat"
|
||||
$ADB logcat -d > ${OUTPUTS_DIR}/logcat.txt
|
||||
}
|
||||
|
||||
run_jvm_tests() {
|
||||
log_info "Running JVM tests"
|
||||
if [ ! -z $RELEASE ]; then
|
||||
$GRADLE testReleaseUnitTest :uhabits-core:check || fail
|
||||
else
|
||||
$GRADLE testDebugUnitTest :uhabits-core:check || fail
|
||||
fi
|
||||
log_info "Running JVM tests"
|
||||
if [ ! -z $RELEASE ]; then
|
||||
$GRADLE testReleaseUnitTest :uhabits-core:check || fail
|
||||
else
|
||||
$GRADLE testDebugUnitTest :uhabits-core:check || fail
|
||||
fi
|
||||
}
|
||||
|
||||
uninstall_test_apk() {
|
||||
log_info "Uninstalling test APK"
|
||||
$ADB uninstall ${PACKAGE_NAME}.test
|
||||
log_info "Uninstalling test APK"
|
||||
$ADB uninstall ${PACKAGE_NAME}.test
|
||||
}
|
||||
|
||||
fetch_images() {
|
||||
log_info "Fetching images"
|
||||
rm -rf $OUTPUTS_DIR/test-screenshots
|
||||
$ADB pull /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots/ $OUTPUTS_DIR
|
||||
$ADB shell rm -r /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots/
|
||||
log_info "Fetching images"
|
||||
rm -rf $OUTPUTS_DIR/test-screenshots
|
||||
$ADB pull /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots/ $OUTPUTS_DIR
|
||||
$ADB shell rm -r /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots/
|
||||
}
|
||||
|
||||
accept_images() {
|
||||
find tmp/test-screenshots -name '*.expected*' -delete
|
||||
rsync -av tmp/test-screenshots/ uhabits-android/src/androidTest/assets/
|
||||
find tmp/test-screenshots -name '*.expected*' -delete
|
||||
rsync -av tmp/test-screenshots/ uhabits-android/src/androidTest/assets/
|
||||
}
|
||||
|
||||
run_tests() {
|
||||
SIZE=$1
|
||||
run_adb_as_root
|
||||
install_test_butler
|
||||
uninstall_apk
|
||||
install_apk
|
||||
install_test_apk
|
||||
run_instrumented_tests $SIZE
|
||||
parse_instrumentation_results
|
||||
fetch_logcat
|
||||
uninstall_test_apk
|
||||
SIZE=$1
|
||||
run_adb_as_root
|
||||
install_test_butler
|
||||
uninstall_apk
|
||||
install_apk
|
||||
install_test_apk
|
||||
run_instrumented_tests $SIZE
|
||||
parse_instrumentation_results
|
||||
fetch_logcat
|
||||
uninstall_test_apk
|
||||
}
|
||||
|
||||
parse_opts() {
|
||||
OPTS=`getopt -o ur --long uninstall-first,release -n 'build.sh' -- "$@"`
|
||||
if [ $? != 0 ] ; then exit 1; fi
|
||||
eval set -- "$OPTS"
|
||||
OPTS=`getopt -o r --long release -n 'build.sh' -- "$@"`
|
||||
if [ $? != 0 ] ; then exit 1; fi
|
||||
eval set -- "$OPTS"
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
-u | --uninstall-first ) UNINSTALL_FIRST=1; shift ;;
|
||||
-r | --release ) RELEASE=1; shift ;;
|
||||
* ) break ;;
|
||||
esac
|
||||
done
|
||||
while true; do
|
||||
case "$1" in
|
||||
-r | --release ) RELEASE=1; shift ;;
|
||||
* ) break ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
remove_build_dir() {
|
||||
rm -rfv .gradle
|
||||
rm -rfv build
|
||||
rm -rfv android-base/build
|
||||
rm -rfv android-pickers/build
|
||||
rm -rfv uhabits-android/build
|
||||
rm -rfv uhabits-core/build
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
build)
|
||||
shift; parse_opts $*
|
||||
build)
|
||||
shift; parse_opts $*
|
||||
|
||||
build_apk
|
||||
build_instrumentation_apk
|
||||
run_jvm_tests
|
||||
#generate_coverage_badge
|
||||
;;
|
||||
build_apk
|
||||
build_instrumentation_apk
|
||||
run_jvm_tests
|
||||
#generate_coverage_badge
|
||||
;;
|
||||
|
||||
ci-tests)
|
||||
if [ -z $3 ]; then
|
||||
cat <<- END
|
||||
Usage: $0 ci-tests AVD_NAME AVD_SERIAL [options]
|
||||
medium-tests)
|
||||
shift; parse_opts $*
|
||||
run_tests medium
|
||||
;;
|
||||
|
||||
Parameters:
|
||||
AVD_NAME name of the virtual android device to start
|
||||
AVD_SERIAL adb port to use (e.g. 5560)
|
||||
large-tests)
|
||||
shift; parse_opts $*
|
||||
run_tests large
|
||||
;;
|
||||
|
||||
Options:
|
||||
-u --uninstall-first Uninstall existing APK first
|
||||
-r --release Test release APK, instead of debug
|
||||
END
|
||||
exit 1
|
||||
fi
|
||||
fetch-images)
|
||||
fetch_images
|
||||
;;
|
||||
|
||||
shift; AVD_NAME=$1
|
||||
shift; AVD_SERIAL=$1
|
||||
shift; parse_opts $*
|
||||
ADB="${ADB} -s emulator-${AVD_SERIAL}"
|
||||
accept-images)
|
||||
accept_images
|
||||
;;
|
||||
|
||||
start_emulator
|
||||
run_tests medium
|
||||
stop_emulator
|
||||
stop_gradle_daemon
|
||||
;;
|
||||
install)
|
||||
shift; parse_opts $*
|
||||
build_apk
|
||||
install_apk
|
||||
;;
|
||||
|
||||
clean)
|
||||
remove_build_dir
|
||||
;;
|
||||
|
||||
medium-tests)
|
||||
shift; parse_opts $*
|
||||
run_tests medium
|
||||
;;
|
||||
*)
|
||||
cat <<END
|
||||
Usage: $0 <command> [options]
|
||||
Builds, installs and tests Loop Habit Tracker
|
||||
|
||||
large-tests)
|
||||
shift; parse_opts $*
|
||||
run_tests large
|
||||
;;
|
||||
Commands:
|
||||
accept-images Copies fetched images to corresponding assets folder
|
||||
build Build APK and run JVM tests
|
||||
clean Remove build directory
|
||||
fetch-images Fetches failed view test images from device
|
||||
install Install app on connected device
|
||||
large-tests Run large-sized tests on connected device
|
||||
medium-tests Run medium-sized tests on connected device
|
||||
|
||||
fetch-images)
|
||||
fetch_images
|
||||
;;
|
||||
|
||||
accept-images)
|
||||
accept_images
|
||||
;;
|
||||
|
||||
install)
|
||||
shift; parse_opts $*
|
||||
build_apk
|
||||
install_apk
|
||||
;;
|
||||
|
||||
*)
|
||||
cat <<- END
|
||||
Usage: $0 <command> [options]
|
||||
Builds, installs and tests Loop Habit Tracker
|
||||
|
||||
Commands:
|
||||
ci-tests Start emulator silently, run tests then kill emulator
|
||||
local-tests Run all tests on connected device
|
||||
install Install app on connected device
|
||||
fetch-images Fetches failed view test images from device
|
||||
accept-images Copies fetched images to corresponding assets folder
|
||||
|
||||
Options:
|
||||
-r --release Build and test release APK, instead of debug
|
||||
END
|
||||
exit 1
|
||||
Options:
|
||||
-r --release Build and test release APK, instead of debug
|
||||
END
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -9,7 +9,7 @@ DAGGER_VERSION = 2.25.4
|
||||
KOTLIN_VERSION = 1.3.61
|
||||
SUPPORT_LIBRARY_VERSION = 28.0.0
|
||||
AUTO_FACTORY_VERSION = 1.0-beta6
|
||||
BUILD_TOOLS_VERSION = 3.5.3
|
||||
BUILD_TOOLS_VERSION = 4.0.0
|
||||
|
||||
org.gradle.parallel=false
|
||||
org.gradle.daemon=true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
||||
|
||||
@@ -55,7 +55,7 @@ def get_total(report):
|
||||
|
||||
def get_color(total):
|
||||
"""
|
||||
Return color for current coverage precent
|
||||
Return color for current coverage percent
|
||||
"""
|
||||
try:
|
||||
xtotal = int(total)
|
||||
|
||||
@@ -88,6 +88,7 @@ dependencies {
|
||||
implementation "com.google.code.gson:gson:2.8.5"
|
||||
implementation "com.google.code.findbugs:jsr305:3.0.2"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KOTLIN_VERSION"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4"
|
||||
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
compileOnly "javax.annotation:jsr250-api:1.0"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
@@ -39,6 +39,7 @@ import static androidx.test.uiautomator.UiDevice.*;
|
||||
public class BaseUserInterfaceTest
|
||||
{
|
||||
private static final String PKG = "org.isoron.uhabits";
|
||||
public static final String EMPTY_DESCRIPTION_HABIT_NAME = "Read books";
|
||||
|
||||
public static UiDevice device;
|
||||
|
||||
@@ -96,25 +97,29 @@ public class BaseUserInterfaceTest
|
||||
|
||||
Habit h1 = fixtures.createEmptyHabit();
|
||||
h1.setName("Wake up early");
|
||||
h1.setDescription("Did you wake up early today?");
|
||||
h1.setQuestion("Did you wake up early today?");
|
||||
h1.setDescription("test description 1");
|
||||
h1.setColor(5);
|
||||
habitList.update(h1);
|
||||
|
||||
Habit h2 = fixtures.createShortHabit();
|
||||
h2.setName("Track time");
|
||||
h2.setDescription("Did you track your time?");
|
||||
h2.setQuestion("Did you track your time?");
|
||||
h2.setDescription("test description 2");
|
||||
h2.setColor(5);
|
||||
habitList.update(h2);
|
||||
|
||||
Habit h3 = fixtures.createLongHabit();
|
||||
h3.setName("Meditate");
|
||||
h3.setDescription("Did meditate today?");
|
||||
h3.setQuestion("Did meditate today?");
|
||||
h3.setDescription("test description 3");
|
||||
h3.setColor(10);
|
||||
habitList.update(h3);
|
||||
|
||||
Habit h4 = fixtures.createEmptyHabit();
|
||||
h4.setName("Read books");
|
||||
h4.setDescription("Did you read books today?");
|
||||
h4.setName(EMPTY_DESCRIPTION_HABIT_NAME);
|
||||
h4.setQuestion("Did you read books today?");
|
||||
h4.setDescription("");
|
||||
h4.setColor(2);
|
||||
habitList.update(h4);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ public class HabitFixtures
|
||||
{
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName("Meditate");
|
||||
habit.setDescription("Did you meditate this morning?");
|
||||
habit.setQuestion("Did you meditate this morning?");
|
||||
habit.setDescription("This is a test description");
|
||||
habit.setColor(5);
|
||||
habit.setFrequency(Frequency.DAILY);
|
||||
habit.setId(id);
|
||||
@@ -81,7 +82,7 @@ public class HabitFixtures
|
||||
{
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName("Take a walk");
|
||||
habit.setDescription("How many steps did you walk today?");
|
||||
habit.setQuestion("How many steps did you walk today?");
|
||||
habit.setType(Habit.NUMBER_HABIT);
|
||||
habit.setTargetType(Habit.AT_LEAST);
|
||||
habit.setTargetValue(200.0);
|
||||
@@ -103,7 +104,7 @@ public class HabitFixtures
|
||||
{
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName("Wake up early");
|
||||
habit.setDescription("Did you wake up before 6am?");
|
||||
habit.setQuestion("Did you wake up before 6am?");
|
||||
habit.setFrequency(new Frequency(2, 3));
|
||||
habitList.add(habit);
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ class TestModule {
|
||||
@ActivityScope
|
||||
@Component(modules = arrayOf(
|
||||
ActivityContextModule::class,
|
||||
AboutModule::class,
|
||||
HabitsActivityModule::class,
|
||||
ListHabitsModule::class,
|
||||
ShowHabitModule::class,
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
package org.isoron.uhabits.acceptance;
|
||||
|
||||
import androidx.test.filters.*;
|
||||
import androidx.test.runner.*;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
@@ -39,7 +38,16 @@ import static org.isoron.uhabits.acceptance.steps.ListHabitsSteps.*;
|
||||
public class HabitsTest extends BaseUserInterfaceTest
|
||||
{
|
||||
@Test
|
||||
public void shouldCreateHabit() throws Exception
|
||||
public void shouldCreateHabit() throws Exception {
|
||||
shouldCreateHabit("this is a test description");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateHabitBlankDescription() throws Exception {
|
||||
shouldCreateHabit("");
|
||||
}
|
||||
|
||||
private void shouldCreateHabit(String description) throws Exception
|
||||
{
|
||||
launchApp();
|
||||
|
||||
@@ -47,14 +55,16 @@ public class HabitsTest extends BaseUserInterfaceTest
|
||||
clickMenu(ADD);
|
||||
|
||||
verifyShowsScreen(EDIT_HABIT);
|
||||
typeName("Hello world");
|
||||
String testName = "Hello world";
|
||||
typeName(testName);
|
||||
typeQuestion("Did you say hello to the world today?");
|
||||
typeDescription(description);
|
||||
pickFrequency("Every week");
|
||||
pickColor(5);
|
||||
clickSave();
|
||||
|
||||
verifyShowsScreen(LIST_HABITS);
|
||||
verifyDisplaysText("Hello world");
|
||||
verifyDisplaysText(testName);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -81,7 +91,16 @@ public class HabitsTest extends BaseUserInterfaceTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEditHabit() throws Exception
|
||||
public void shouldEditHabit() throws Exception {
|
||||
shouldEditHabit("this is a test description");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEditHabitBlankDescription() throws Exception {
|
||||
shouldEditHabit("");
|
||||
}
|
||||
|
||||
private void shouldEditHabit(String description) throws Exception
|
||||
{
|
||||
launchApp();
|
||||
|
||||
@@ -92,6 +111,7 @@ public class HabitsTest extends BaseUserInterfaceTest
|
||||
verifyShowsScreen(EDIT_HABIT);
|
||||
typeName("Take a walk");
|
||||
typeQuestion("Did you take a walk today?");
|
||||
typeDescription(description);
|
||||
clickSave();
|
||||
|
||||
verifyShowsScreen(LIST_HABITS);
|
||||
@@ -174,4 +194,12 @@ public class HabitsTest extends BaseUserInterfaceTest
|
||||
verifyDisplaysText("Track time");
|
||||
verifyDisplaysText("Wake up early");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHideNotesCard() throws Exception
|
||||
{
|
||||
launchApp();
|
||||
clickText(EMPTY_DESCRIPTION_HABIT_NAME);
|
||||
verifyShowsScreen(SHOW_HABIT, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,16 @@
|
||||
|
||||
package org.isoron.uhabits.acceptance.steps;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.test.espresso.*;
|
||||
import androidx.test.espresso.contrib.*;
|
||||
import androidx.test.uiautomator.*;
|
||||
import androidx.appcompat.widget.*;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.activities.habits.list.*;
|
||||
@@ -153,7 +155,11 @@ public class CommonSteps extends BaseUserInterfaceTest
|
||||
LIST_HABITS, SHOW_HABIT, EDIT_HABIT
|
||||
}
|
||||
|
||||
public static void verifyShowsScreen(Screen screen)
|
||||
public static void verifyShowsScreen(Screen screen) {
|
||||
verifyShowsScreen(screen, true);
|
||||
}
|
||||
|
||||
public static void verifyShowsScreen(Screen screen, boolean notesCardVisibleExpected)
|
||||
{
|
||||
switch(screen)
|
||||
{
|
||||
@@ -163,10 +169,14 @@ public class CommonSteps extends BaseUserInterfaceTest
|
||||
break;
|
||||
|
||||
case SHOW_HABIT:
|
||||
Matcher<View> noteCardViewMatcher = notesCardVisibleExpected ? isDisplayed() :
|
||||
withEffectiveVisibility(Visibility.GONE);
|
||||
onView(withId(R.id.subtitleCard)).check(matches(isDisplayed()));
|
||||
onView(withId(R.id.notesCard)).check(matches(noteCardViewMatcher));
|
||||
break;
|
||||
|
||||
case EDIT_HABIT:
|
||||
onView(withId(R.id.tvQuestion)).check(matches(isDisplayed()));
|
||||
onView(withId(R.id.tvDescription)).check(matches(isDisplayed()));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,12 @@ public class EditHabitSteps
|
||||
|
||||
public static void typeQuestion(String name)
|
||||
{
|
||||
typeTextWithId(R.id.tvDescription, name);
|
||||
typeTextWithId(R.id.tvQuestion, name);
|
||||
}
|
||||
|
||||
public static void typeDescription(String description)
|
||||
{
|
||||
typeTextWithId(R.id.tvDescription, description);
|
||||
}
|
||||
|
||||
public static void setReminder()
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.activities.habits.show.views;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.MediumTest;
|
||||
|
||||
import org.isoron.uhabits.BaseViewTest;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.core.models.Habit;
|
||||
import org.isoron.uhabits.core.models.Reminder;
|
||||
import org.isoron.uhabits.core.models.WeekdayList;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@MediumTest
|
||||
public class NotesCardTest extends BaseViewTest
|
||||
{
|
||||
public static final String PATH = "habits/show/NotesCard/";
|
||||
|
||||
private NotesCard view;
|
||||
|
||||
private Habit habit;
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp()
|
||||
{
|
||||
super.setUp();
|
||||
|
||||
habit = fixtures.createLongHabit();
|
||||
habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY));
|
||||
|
||||
view = LayoutInflater
|
||||
.from(targetContext)
|
||||
.inflate(R.layout.show_habit, null)
|
||||
.findViewById(R.id.notesCard);
|
||||
|
||||
view.setHabit(habit);
|
||||
view.refreshData();
|
||||
|
||||
measureView(view, 800, 200);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender() throws Exception
|
||||
{
|
||||
assertRenders(view, PATH + "render.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRenderEmptyDescription() throws Exception
|
||||
{
|
||||
habit.setDescription("");
|
||||
view.refreshData();
|
||||
assertRenders(view, PATH + "render-empty-description.png");
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public class SubtitleCardTest extends BaseViewTest
|
||||
habit = fixtures.createLongHabit();
|
||||
habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY));
|
||||
|
||||
view = (SubtitleCard) LayoutInflater
|
||||
view = LayoutInflater
|
||||
.from(targetContext)
|
||||
.inflate(R.layout.show_habit, null)
|
||||
.findViewById(R.id.subtitleCard);
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.tasks;
|
||||
|
||||
import androidx.test.filters.*;
|
||||
import androidx.test.runner.*;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.junit.*;
|
||||
import org.junit.runner.*;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@MediumTest
|
||||
public class ExportCSVTaskTest extends BaseAndroidTest
|
||||
{
|
||||
@Before
|
||||
@Override
|
||||
public void setUp()
|
||||
{
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testExportCSV() throws Throwable
|
||||
// {
|
||||
// fixtures.purgeHabits(habitList);
|
||||
// fixtures.createShortHabit();
|
||||
//
|
||||
// List<Habit> selected = new LinkedList<>();
|
||||
// for (Habit h : habitList) selected.add(h);
|
||||
// File outputDir = new AndroidDirFinder(targetContext).getFilesDir("CSV");
|
||||
// assertNotNull(outputDir);
|
||||
//
|
||||
// taskRunner.execute(
|
||||
// new ExportCSVTask(habitList, selected, outputDir, archiveFilename -> {
|
||||
// assertThat(archiveFilename, is(not(nullValue())));
|
||||
// File f = new File(archiveFilename);
|
||||
// assertTrue(f.exists());
|
||||
// assertTrue(f.canRead());
|
||||
// }));
|
||||
// }
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.tasks;
|
||||
|
||||
import androidx.test.filters.*;
|
||||
import androidx.test.runner.*;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.junit.*;
|
||||
import org.junit.runner.*;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@MediumTest
|
||||
public class ExportDBTaskTest extends BaseAndroidTest
|
||||
{
|
||||
@Override
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testExportCSV() throws Throwable
|
||||
// {
|
||||
// ExportDBTask task =
|
||||
// new ExportDBTask(targetContext, new AndroidDirFinder(targetContext),
|
||||
// filename ->
|
||||
// {
|
||||
// assertNotNull(filename);
|
||||
// File f = new File(filename);
|
||||
// assertTrue(f.exists());
|
||||
// assertTrue(f.canRead());
|
||||
// });
|
||||
//
|
||||
// taskRunner.execute(task);
|
||||
// }
|
||||
}
|
||||
@@ -34,15 +34,12 @@ import org.isoron.uhabits.core.ui.screens.habits.list.*
|
||||
@Component(modules = arrayOf(
|
||||
ActivityContextModule::class,
|
||||
BaseActivityModule::class,
|
||||
AboutModule::class,
|
||||
HabitsActivityModule::class,
|
||||
ListHabitsModule::class,
|
||||
ShowHabitModule::class,
|
||||
HabitModule::class
|
||||
), dependencies = arrayOf(HabitsApplicationComponent::class))
|
||||
interface HabitsActivityComponent {
|
||||
val aboutRootView: AboutRootView
|
||||
val aboutScreen: AboutScreen
|
||||
val colorPickerDialogFactory: ColorPickerDialogFactory
|
||||
val habitCardListAdapter: HabitCardListAdapter
|
||||
val listHabitsBehavior: ListHabitsBehavior
|
||||
|
||||
@@ -21,6 +21,7 @@ package org.isoron.uhabits.activities.about;
|
||||
|
||||
import android.os.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.activities.*;
|
||||
|
||||
/**
|
||||
@@ -33,8 +34,12 @@ public class AboutActivity extends HabitsActivity
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
AboutScreen screen = getComponent().getAboutScreen();
|
||||
screen.setRootView(getComponent().getAboutRootView());
|
||||
HabitsApplication app = (HabitsApplication) getApplication();
|
||||
AboutScreen screen = new AboutScreen(this,
|
||||
app.getComponent().getIntentFactory(),
|
||||
app.getComponent().getPreferences());
|
||||
AboutRootView rootView = new AboutRootView(this, screen);
|
||||
screen.setRootView(rootView);
|
||||
setScreen(screen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,12 @@ package org.isoron.uhabits.activities.about;
|
||||
import android.content.*;
|
||||
import android.widget.*;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.*;
|
||||
|
||||
import org.isoron.androidbase.activities.*;
|
||||
import org.isoron.androidbase.utils.*;
|
||||
import org.isoron.uhabits.BuildConfig;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.core.ui.screens.about.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
@@ -40,14 +39,14 @@ public class AboutRootView extends BaseRootView
|
||||
TextView tvVersion;
|
||||
|
||||
@NonNull
|
||||
private final AboutBehavior behavior;
|
||||
private final AboutScreen screen;
|
||||
|
||||
@Inject
|
||||
public AboutRootView(@NonNull @ActivityContext Context context,
|
||||
@NonNull AboutBehavior behavior)
|
||||
@NonNull AboutScreen screen)
|
||||
{
|
||||
super(context);
|
||||
this.behavior = behavior;
|
||||
this.screen = screen;
|
||||
|
||||
addView(inflate(getContext(), R.layout.about, null));
|
||||
ButterKnife.bind(this);
|
||||
@@ -71,46 +70,45 @@ public class AboutRootView extends BaseRootView
|
||||
@OnClick(R.id.tvFeedback)
|
||||
public void onClickFeedback()
|
||||
{
|
||||
behavior.onSendFeedback();
|
||||
screen.showSendFeedbackScreen();
|
||||
}
|
||||
|
||||
@OnClick(R.id.tvVersion)
|
||||
public void onClickIcon()
|
||||
{
|
||||
behavior.onPressDeveloperCountdown();
|
||||
screen.onPressDeveloperCountdown();
|
||||
}
|
||||
|
||||
@OnClick(R.id.tvRate)
|
||||
public void onClickRate()
|
||||
{
|
||||
behavior.onRateApp();
|
||||
screen.showRateAppWebsite();
|
||||
}
|
||||
|
||||
@OnClick(R.id.tvSource)
|
||||
public void onClickSource()
|
||||
{
|
||||
behavior.onViewSourceCode();
|
||||
screen.showSourceCodeWebsite();
|
||||
}
|
||||
|
||||
@OnClick(R.id.tvTranslate)
|
||||
public void onClickTranslate()
|
||||
{
|
||||
behavior.onTranslateApp();
|
||||
screen.showTranslationWebsite();
|
||||
}
|
||||
|
||||
@OnClick(R.id.tvPrivacy)
|
||||
public void onClickPrivacy()
|
||||
{
|
||||
behavior.onClickPrivacy();
|
||||
screen.showPrivacyPolicyWebsite();
|
||||
}
|
||||
|
||||
@OnClick(R.id.tvContributors)
|
||||
public void onClickContributors()
|
||||
{
|
||||
behavior.onClickCodeContributors();
|
||||
screen.showCodeContributorsWebsite();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void initToolbar()
|
||||
{
|
||||
|
||||
@@ -21,69 +21,67 @@ package org.isoron.uhabits.activities.about;
|
||||
|
||||
import android.widget.*;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.*;
|
||||
|
||||
import org.isoron.androidbase.activities.*;
|
||||
import org.isoron.uhabits.core.ui.screens.about.*;
|
||||
import org.isoron.uhabits.core.preferences.*;
|
||||
import org.isoron.uhabits.intents.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
import static org.isoron.uhabits.core.ui.screens.about.AboutBehavior.Message.*;
|
||||
|
||||
public class AboutScreen extends BaseScreen implements AboutBehavior.Screen
|
||||
public class AboutScreen extends BaseScreen
|
||||
{
|
||||
@NonNull
|
||||
private final Preferences prefs;
|
||||
|
||||
private int developerCountdown = 5;
|
||||
|
||||
@NonNull
|
||||
private final IntentFactory intents;
|
||||
|
||||
@Inject
|
||||
public AboutScreen(@NonNull BaseActivity activity,
|
||||
@NonNull IntentFactory intents)
|
||||
@NonNull IntentFactory intents,
|
||||
@NonNull Preferences prefs)
|
||||
{
|
||||
super(activity);
|
||||
this.intents = intents;
|
||||
this.prefs = prefs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMessage(AboutBehavior.Message message)
|
||||
{
|
||||
if (message == YOU_ARE_NOW_A_DEVELOPER) Toast
|
||||
.makeText(activity, "You are now a developer", Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showRateAppWebsite()
|
||||
{
|
||||
activity.startActivity(intents.rateApp(activity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showSendFeedbackScreen()
|
||||
{
|
||||
activity.startActivity(intents.sendFeedback(activity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showSourceCodeWebsite()
|
||||
{
|
||||
activity.startActivity(intents.viewSourceCode(activity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showTranslationWebsite()
|
||||
{
|
||||
activity.startActivity(intents.helpTranslate(activity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showPrivacyPolicyWebsite()
|
||||
{
|
||||
activity.startActivity(intents.privacyPolicy(activity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showCodeContributorsWebsite() {
|
||||
public void showCodeContributorsWebsite()
|
||||
{
|
||||
activity.startActivity(intents.codeContributors(activity));
|
||||
}
|
||||
|
||||
public void onPressDeveloperCountdown()
|
||||
{
|
||||
developerCountdown--;
|
||||
if (developerCountdown == 0) {
|
||||
prefs.setDeveloper(true);
|
||||
Toast.makeText(activity, "You are now a developer", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +187,7 @@ public class EditHabitDialog extends AppCompatDialogFragment
|
||||
habit.copyFrom(originalHabit);
|
||||
habit.setName(namePanel.getName());
|
||||
habit.setDescription(namePanel.getDescription());
|
||||
habit.setQuestion(namePanel.getQuestion());
|
||||
habit.setColor(namePanel.getColor());
|
||||
habit.setReminder(reminderPanel.getReminder());
|
||||
habit.setFrequency(frequencyPanel.getFrequency());
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.text.*;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatEditText;
|
||||
|
||||
@@ -82,7 +83,7 @@ public class ExampleEditText extends AppCompatEditText
|
||||
updateText();
|
||||
}
|
||||
|
||||
public void setRealText(String realText)
|
||||
public void setRealText(@NonNull String realText)
|
||||
{
|
||||
this.realText = realText;
|
||||
updateText();
|
||||
|
||||
@@ -42,6 +42,9 @@ public class NameDescriptionPanel extends FrameLayout
|
||||
@BindView(R.id.tvName)
|
||||
EditText tvName;
|
||||
|
||||
@BindView(R.id.tvQuestion)
|
||||
ExampleEditText tvQuestion;
|
||||
|
||||
@BindView(R.id.tvDescription)
|
||||
ExampleEditText tvDescription;
|
||||
|
||||
@@ -79,6 +82,12 @@ public class NameDescriptionPanel extends FrameLayout
|
||||
return tvDescription.getRealText().trim();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getQuestion()
|
||||
{
|
||||
return tvQuestion.getRealText().trim();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getName()
|
||||
{
|
||||
@@ -90,12 +99,13 @@ public class NameDescriptionPanel extends FrameLayout
|
||||
Resources res = getResources();
|
||||
|
||||
if(habit.isNumerical())
|
||||
tvDescription.setExample(res.getString(R.string.example_question_numerical));
|
||||
tvQuestion.setExample(res.getString(R.string.example_question_numerical));
|
||||
else
|
||||
tvDescription.setExample(res.getString(R.string.example_question_boolean));
|
||||
tvQuestion.setExample(res.getString(R.string.example_question_boolean));
|
||||
|
||||
setColor(habit.getColor());
|
||||
tvName.setText(habit.getName());
|
||||
tvQuestion.setRealText(habit.getQuestion());
|
||||
tvDescription.setRealText(habit.getDescription());
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ package org.isoron.uhabits.activities.habits.show;
|
||||
|
||||
import android.content.*;
|
||||
import android.os.*;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.*;
|
||||
@@ -52,6 +53,12 @@ public class ShowHabitRootView extends BaseRootView
|
||||
@BindView(R.id.subtitleCard)
|
||||
SubtitleCard subtitleCard;
|
||||
|
||||
@BindView(R.id.notesCard)
|
||||
NotesCard notesCard;
|
||||
|
||||
@BindView(R.id.habitNotes)
|
||||
TextView habitNotes;
|
||||
|
||||
@BindView(R.id.overviewCard)
|
||||
OverviewCard overviewCard;
|
||||
|
||||
@@ -136,6 +143,7 @@ public class ShowHabitRootView extends BaseRootView
|
||||
private void initCards()
|
||||
{
|
||||
subtitleCard.setHabit(habit);
|
||||
notesCard.setHabit(habit);
|
||||
overviewCard.setHabit(habit);
|
||||
scoreCard.setHabit(habit);
|
||||
historyCard.setHabit(habit);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.isoron.uhabits.activities.habits.show.views
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import org.isoron.uhabits.R
|
||||
import org.isoron.uhabits.core.tasks.Task
|
||||
|
||||
class NotesCard(context: Context?, attrs: AttributeSet?) : HabitCard(context, attrs) {
|
||||
|
||||
private val notesTextView: TextView
|
||||
|
||||
init {
|
||||
View.inflate(getContext(), R.layout.show_habit_notes, this)
|
||||
notesTextView = findViewById(R.id.habitNotes)
|
||||
}
|
||||
|
||||
override fun refreshData() {
|
||||
notesTextView.text = habit.description
|
||||
visibility = if(habit.description.isEmpty()) View.GONE else View.VISIBLE
|
||||
notesTextView.visibility = visibility
|
||||
}
|
||||
|
||||
override fun createRefreshTask(): Task = error("refresh task should never be called.")
|
||||
}
|
||||
@@ -59,12 +59,12 @@ public class SubtitleCard extends HabitCard
|
||||
questionLabel.setVisibility(VISIBLE);
|
||||
|
||||
questionLabel.setTextColor(color);
|
||||
questionLabel.setText(habit.getDescription());
|
||||
questionLabel.setText(habit.getQuestion());
|
||||
frequencyLabel.setText(toText(habit.getFrequency()));
|
||||
|
||||
if (habit.hasReminder()) updateReminderText(habit.getReminder());
|
||||
|
||||
if (habit.getDescription().isEmpty()) questionLabel.setVisibility(GONE);
|
||||
if (habit.getQuestion().isEmpty()) questionLabel.setVisibility(GONE);
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ class AndroidNotificationTray
|
||||
val builder = NotificationCompat.Builder(context, REMINDERS_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle(habit.name)
|
||||
.setContentText(if(habit.description.isBlank()) defaultText else habit.description)
|
||||
.setContentText(if(habit.question.isBlank()) defaultText else habit.question)
|
||||
.setContentIntent(pendingIntents.showHabit(habit))
|
||||
.setDeleteIntent(pendingIntents.dismissNotification(habit))
|
||||
.addAction(addRepetitionAction)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
@@ -18,53 +17,82 @@
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://isoron.org/android"
|
||||
xmlns:app1="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
style="@style/dialogFormRow">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="6">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/tvName"
|
||||
style="@style/dialogFormInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/name">
|
||||
|
||||
<requestFocus/>
|
||||
</EditText>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/buttonPickColor"
|
||||
style="@style/dialogFormInputColor"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/color_picker_default_title"
|
||||
android:src="?dialogIconChangeColor"/>
|
||||
|
||||
</LinearLayout>
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="300dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:id="@+id/tilName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app1:layout_constraintEnd_toStartOf="@+id/buttonPickColor"
|
||||
app1:layout_constraintHorizontal_weight="6"
|
||||
app1:layout_constraintStart_toStartOf="parent"
|
||||
app1:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/tvName"
|
||||
style="@style/dialogFormInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="start"
|
||||
android:gravity="center_vertical"
|
||||
android:hint="@string/name">
|
||||
|
||||
<requestFocus />
|
||||
</EditText>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/buttonPickColor"
|
||||
style="@style/dialogFormInputColor"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/color_picker_default_title"
|
||||
android:src="?dialogIconChangeColor"
|
||||
app1:layout_constraintBottom_toBottomOf="@id/tilName"
|
||||
app1:layout_constraintEnd_toEndOf="parent"
|
||||
app1:layout_constraintHorizontal_weight="1"
|
||||
app1:layout_constraintStart_toEndOf="@+id/tilName"
|
||||
app1:layout_constraintTop_toTopOf="@id/tilName" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilQuestion"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/question"
|
||||
app1:layout_constraintEnd_toEndOf="parent"
|
||||
app1:layout_constraintStart_toStartOf="parent"
|
||||
app1:layout_constraintTop_toBottomOf="@id/tilName">
|
||||
|
||||
<org.isoron.uhabits.activities.habits.edit.views.ExampleEditText
|
||||
android:id="@+id/tvQuestion"
|
||||
style="@style/dialogFormInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:example="@string/example_question_numerical" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:hint="@string/notes"
|
||||
app1:layout_constraintBottom_toBottomOf="parent"
|
||||
app1:layout_constraintEnd_toEndOf="parent"
|
||||
app1:layout_constraintStart_toStartOf="parent"
|
||||
app1:layout_constraintTop_toBottomOf="@id/tilQuestion">
|
||||
|
||||
<org.isoron.uhabits.activities.habits.edit.views.ExampleEditText
|
||||
android:id="@+id/tvDescription"
|
||||
style="@style/dialogFormInputMultiline"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/question"
|
||||
app:example="@string/example_question_numerical"/>
|
||||
android:gravity="top"
|
||||
app:example="@string/example_notes" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -38,6 +38,11 @@
|
||||
android:id="@+id/subtitleCard"
|
||||
style="@style/ShowHabit.Subtitle"/>
|
||||
|
||||
<org.isoron.uhabits.activities.habits.show.views.NotesCard
|
||||
android:id="@+id/notesCard"
|
||||
style="@style/Card"
|
||||
android:gravity="center" />
|
||||
|
||||
<View
|
||||
android:id="@+id/headerShadow"
|
||||
style="@style/ToolbarShadow"/>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:layout_height="wrap_content"
|
||||
tools:layout_width="match_parent"
|
||||
tools:orientation="vertical"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/habitNotes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textColor="?highContrastTextColor"
|
||||
android:visibility="gone"
|
||||
tools:text="This is some example text for the notes" />
|
||||
</merge>
|
||||
@@ -18,7 +18,12 @@
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:parentTag="android.widget.LinearLayout"
|
||||
tools:orientation="vertical"
|
||||
tools:layout_width="match_parent"
|
||||
tools:layout_height="wrap_content">
|
||||
<TextView
|
||||
android:id="@+id/questionLabel"
|
||||
android:layout_width="match_parent"
|
||||
@@ -26,6 +31,7 @@
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textColor="?mediumContrastTextColor"
|
||||
android:textSize="@dimen/regularTextSize"
|
||||
tools:text="Have you worked out today?"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
@@ -33,7 +39,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="horizontal"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/frequencyIcon"
|
||||
@@ -42,8 +49,7 @@
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginRight="5dp"
|
||||
android:alpha="0.3"
|
||||
android:src="?iconFrequency"
|
||||
/>
|
||||
android:src="?iconFrequency" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/frequencyLabel"
|
||||
@@ -51,20 +57,18 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/every_day"
|
||||
android:textColor="?mediumContrastTextColor"
|
||||
android:textSize="@dimen/smallTextSize"
|
||||
/>
|
||||
android:textSize="@dimen/smallTextSize" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/reminderIcon"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="5dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginRight="5dp"
|
||||
android:alpha="0.3"
|
||||
android:src="?iconReminder"
|
||||
/>
|
||||
android:src="?iconReminder" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/reminderLabel"
|
||||
@@ -72,8 +76,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="1dp"
|
||||
android:textColor="?mediumContrastTextColor"
|
||||
android:textSize="@dimen/smallTextSize"
|
||||
/>
|
||||
android:textSize="@dimen/smallTextSize" />
|
||||
|
||||
</LinearLayout>
|
||||
</merge>
|
||||
@@ -123,7 +123,7 @@
|
||||
<string-array name="targetIntervals" translatable="false">
|
||||
<item>daily</item>
|
||||
<item>weekly</item>
|
||||
<item>montly</item>
|
||||
<item>monthly</item>
|
||||
</string-array>
|
||||
|
||||
<string name="snooze_interval_default" translatable="false">15</string>
|
||||
|
||||
@@ -244,5 +244,7 @@
|
||||
<string name="widget_opacity_description">Makes widgets more transparent or more opaque in your home screen.</string>
|
||||
<string name="first_day_of_the_week">First day of the week</string>
|
||||
<string name="default_reminder_question">Have you completed this habit today?</string>
|
||||
<string name="notes">Notes</string>
|
||||
<string name="example_notes">You can put whatever you want here!</string>
|
||||
|
||||
</resources>
|
||||
@@ -1,5 +1,6 @@
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
annotationProcessor "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION"
|
||||
@@ -26,6 +27,7 @@ dependencies {
|
||||
implementation('com.opencsv:opencsv:3.10') {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
}
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION"
|
||||
}
|
||||
|
||||
sourceCompatibility = "1.8"
|
||||
|
||||
@@ -22,5 +22,5 @@ package org.isoron.uhabits.core;
|
||||
public class Config
|
||||
{
|
||||
public static final String DATABASE_FILENAME = "uhabits.db";
|
||||
public static int DATABASE_VERSION = 22;
|
||||
public static int DATABASE_VERSION = 23;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public class Repository<T>
|
||||
* Returns all records matching the given SQL query.
|
||||
* <p>
|
||||
* The query should only contain the "where" part of the SQL query, and
|
||||
* optinally the "order by" part. "Group by" is not allowed. If no matching
|
||||
* optionally the "order by" part. "Group by" is not allowed. If no matching
|
||||
* records are found, returns an empty list.
|
||||
*/
|
||||
@NonNull
|
||||
|
||||
@@ -87,7 +87,7 @@ public class HabitBullCSVImporter extends AbstractImporter
|
||||
{
|
||||
h = modelFactory.buildHabit();
|
||||
h.setName(name);
|
||||
h.setDescription(description);
|
||||
h.setDescription(description == null ? "" : description);
|
||||
h.setFrequency(Frequency.DAILY);
|
||||
habitList.add(h);
|
||||
map.put(name, h);
|
||||
|
||||
@@ -101,7 +101,7 @@ public class RewireDBImporter extends AbstractImporter
|
||||
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName(name);
|
||||
habit.setDescription(description);
|
||||
habit.setDescription(description == null ? "" : description);
|
||||
|
||||
int periods[] = { 7, 31, 365 };
|
||||
int numerator, denominator;
|
||||
|
||||
@@ -127,7 +127,7 @@ public class TickmateDBImporter extends AbstractImporter
|
||||
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName(name);
|
||||
habit.setDescription(description);
|
||||
habit.setDescription(description == null ? "" : description);
|
||||
habit.setFrequency(Frequency.DAILY);
|
||||
habitList.add(habit);
|
||||
|
||||
|
||||
@@ -354,6 +354,15 @@ public class Habit
|
||||
data.position = newPosition;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getQuestion() {
|
||||
return data.question;
|
||||
}
|
||||
|
||||
public void setQuestion(@NonNull String question) {
|
||||
data.question = question;
|
||||
}
|
||||
|
||||
public static final class HabitData
|
||||
{
|
||||
@NonNull
|
||||
@@ -362,6 +371,9 @@ public class Habit
|
||||
@NonNull
|
||||
public String description;
|
||||
|
||||
@NonNull
|
||||
public String question;
|
||||
|
||||
@NonNull
|
||||
public Frequency frequency;
|
||||
|
||||
@@ -391,6 +403,7 @@ public class Habit
|
||||
this.type = YES_NO_HABIT;
|
||||
this.name = "";
|
||||
this.description = "";
|
||||
this.question = "";
|
||||
this.targetType = AT_LEAST;
|
||||
this.targetValue = 100;
|
||||
this.unit = "";
|
||||
@@ -401,6 +414,7 @@ public class Habit
|
||||
{
|
||||
this.name = model.name;
|
||||
this.description = model.description;
|
||||
this.question = model.question;
|
||||
this.frequency = model.frequency;
|
||||
this.color = model.color;
|
||||
this.archived = model.archived;
|
||||
@@ -427,6 +441,7 @@ public class Habit
|
||||
.append("unit", unit)
|
||||
.append("reminder", reminder)
|
||||
.append("position", position)
|
||||
.append("question", question)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@@ -451,6 +466,7 @@ public class Habit
|
||||
.append(unit, habitData.unit)
|
||||
.append(reminder, habitData.reminder)
|
||||
.append(position, habitData.position)
|
||||
.append(question, habitData.question)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@@ -469,6 +485,7 @@ public class Habit
|
||||
.append(unit)
|
||||
.append(reminder)
|
||||
.append(position)
|
||||
.append(question)
|
||||
.toHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +212,7 @@ public abstract class HabitList implements Iterable<Habit>
|
||||
String header[] = {
|
||||
"Position",
|
||||
"Name",
|
||||
"Question",
|
||||
"Description",
|
||||
"NumRepetitions",
|
||||
"Interval",
|
||||
@@ -228,6 +229,7 @@ public abstract class HabitList implements Iterable<Habit>
|
||||
String[] cols = {
|
||||
String.format("%03d", indexOf(habit) + 1),
|
||||
habit.getName(),
|
||||
habit.getQuestion(),
|
||||
habit.getDescription(),
|
||||
Integer.toString(freq.getNumerator()),
|
||||
Integer.toString(freq.getDenominator()),
|
||||
|
||||
@@ -123,7 +123,7 @@ public abstract class StreakList
|
||||
* habit to not performing a habit, and vice-versa.
|
||||
*
|
||||
* @param beginning the timestamp for the first checkmark
|
||||
* @param checks the checkmarks, ordered by decresing timestamp
|
||||
* @param checks the checkmarks, ordered by decreasing timestamp
|
||||
* @return the list of transitions
|
||||
*/
|
||||
@NonNull
|
||||
|
||||
@@ -33,6 +33,9 @@ public class HabitRecord
|
||||
@Column
|
||||
public String description;
|
||||
|
||||
@Column
|
||||
public String question;
|
||||
|
||||
@Column
|
||||
public String name;
|
||||
|
||||
@@ -91,6 +94,7 @@ public class HabitRecord
|
||||
this.targetValue = model.getTargetValue();
|
||||
this.unit = model.getUnit();
|
||||
this.position = model.getPosition();
|
||||
this.question = model.getQuestion();
|
||||
|
||||
Frequency freq = model.getFrequency();
|
||||
this.freqNum = freq.getNumerator();
|
||||
@@ -113,6 +117,7 @@ public class HabitRecord
|
||||
habit.setId(this.id);
|
||||
habit.setName(this.name);
|
||||
habit.setDescription(this.description);
|
||||
habit.setQuestion(this.question);
|
||||
habit.setFrequency(new Frequency(this.freqNum, this.freqDen));
|
||||
habit.setColor(this.color);
|
||||
habit.setArchived(this.archived != 0);
|
||||
|
||||
@@ -43,7 +43,7 @@ public class HabitFixtures
|
||||
{
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName("Meditate");
|
||||
habit.setDescription("Did you meditate this morning?");
|
||||
habit.setQuestion("Did you meditate this morning?");
|
||||
habit.setColor(3);
|
||||
habit.setFrequency(Frequency.DAILY);
|
||||
saveIfSQLite(habit);
|
||||
@@ -73,7 +73,7 @@ public class HabitFixtures
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setType(Habit.NUMBER_HABIT);
|
||||
habit.setName("Run");
|
||||
habit.setDescription("How many miles did you run today?");
|
||||
habit.setQuestion("How many miles did you run today?");
|
||||
habit.setUnit("miles");
|
||||
habit.setTargetType(Habit.AT_LEAST);
|
||||
habit.setTargetValue(2.0);
|
||||
@@ -98,7 +98,7 @@ public class HabitFixtures
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setType(Habit.NUMBER_HABIT);
|
||||
habit.setName("Walk");
|
||||
habit.setDescription("How many steps did you walk today?");
|
||||
habit.setQuestion("How many steps did you walk today?");
|
||||
habit.setUnit("steps");
|
||||
habit.setTargetType(Habit.AT_LEAST);
|
||||
habit.setTargetValue(100);
|
||||
@@ -133,7 +133,7 @@ public class HabitFixtures
|
||||
{
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName("Wake up early");
|
||||
habit.setDescription("Did you wake up before 6am?");
|
||||
habit.setQuestion("Did you wake up before 6am?");
|
||||
habit.setFrequency(new Frequency(2, 3));
|
||||
saveIfSQLite(habit);
|
||||
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.core.ui.screens.about;
|
||||
|
||||
import androidx.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.preferences.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
public class AboutBehavior
|
||||
{
|
||||
private int developerCountdown = 5;
|
||||
|
||||
@NonNull
|
||||
private Preferences prefs;
|
||||
|
||||
@NonNull
|
||||
private Screen screen;
|
||||
|
||||
@Inject
|
||||
public AboutBehavior(@NonNull Preferences prefs, @NonNull Screen screen)
|
||||
{
|
||||
this.prefs = prefs;
|
||||
this.screen = screen;
|
||||
}
|
||||
|
||||
public void onPressDeveloperCountdown()
|
||||
{
|
||||
developerCountdown--;
|
||||
if (developerCountdown == 0)
|
||||
{
|
||||
prefs.setDeveloper(true);
|
||||
screen.showMessage(Message.YOU_ARE_NOW_A_DEVELOPER);
|
||||
}
|
||||
}
|
||||
|
||||
public void onRateApp()
|
||||
{
|
||||
screen.showRateAppWebsite();
|
||||
}
|
||||
|
||||
public void onSendFeedback()
|
||||
{
|
||||
screen.showSendFeedbackScreen();
|
||||
}
|
||||
|
||||
public void onTranslateApp()
|
||||
{
|
||||
screen.showTranslationWebsite();
|
||||
}
|
||||
|
||||
public void onViewSourceCode()
|
||||
{
|
||||
screen.showSourceCodeWebsite();
|
||||
}
|
||||
|
||||
public void onClickPrivacy() {
|
||||
screen.showPrivacyPolicyWebsite();
|
||||
}
|
||||
|
||||
public void onClickCodeContributors() {
|
||||
screen.showCodeContributorsWebsite();
|
||||
}
|
||||
|
||||
public enum Message
|
||||
{
|
||||
YOU_ARE_NOW_A_DEVELOPER
|
||||
}
|
||||
|
||||
public interface Screen
|
||||
{
|
||||
void showMessage(Message message);
|
||||
|
||||
void showRateAppWebsite();
|
||||
|
||||
void showSendFeedbackScreen();
|
||||
|
||||
void showSourceCodeWebsite();
|
||||
|
||||
void showTranslationWebsite();
|
||||
|
||||
void showPrivacyPolicyWebsite();
|
||||
|
||||
void showCodeContributorsWebsite();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table Habits add column question text;
|
||||
|
||||
update Habits set question = description;
|
||||
|
||||
update Habits set description = "";
|
||||
@@ -125,7 +125,7 @@ public class BaseUnitTest
|
||||
DriverManager.getConnection("jdbc:sqlite::memory:"));
|
||||
db.execute("pragma user_version=8;");
|
||||
MigrationHelper helper = new MigrationHelper(db);
|
||||
helper.migrateTo(21);
|
||||
helper.migrateTo(23);
|
||||
return db;
|
||||
}
|
||||
catch (SQLException e)
|
||||
|
||||
@@ -159,21 +159,4 @@ public class Version22Test extends BaseUnitTest
|
||||
db.execute("insert into repetitions(habit, timestamp, value)" +
|
||||
"values (0, 100, 5)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeepHabitsUnchanged() throws Exception
|
||||
{
|
||||
Habit original = fixtures.createLongHabit();
|
||||
Reminder reminder = new Reminder(8, 30, new WeekdayList(100));
|
||||
original.setReminder(reminder);
|
||||
habitList.update(original);
|
||||
|
||||
helper.migrateTo(22);
|
||||
|
||||
((SQLiteHabitList) habitList).reload();
|
||||
Habit modified = habitList.getById(original.getId());
|
||||
assertNotNull(modified);
|
||||
|
||||
assertThat(original.getData(), equalTo(modified.getData()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.isoron.uhabits.core.database.migrations
|
||||
|
||||
import org.hamcrest.MatcherAssert
|
||||
import org.hamcrest.Matchers
|
||||
import org.isoron.uhabits.core.BaseUnitTest
|
||||
import org.isoron.uhabits.core.database.Database
|
||||
import org.isoron.uhabits.core.database.MigrationHelper
|
||||
import org.isoron.uhabits.core.models.sqlite.SQLModelFactory
|
||||
import org.isoron.uhabits.core.test.HabitFixtures
|
||||
import org.junit.Test
|
||||
|
||||
class Version23Test: BaseUnitTest() {
|
||||
|
||||
private lateinit var db: Database
|
||||
|
||||
private lateinit var helper: MigrationHelper
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
db = openDatabaseResource("/databases/022.db")
|
||||
helper = MigrationHelper(db)
|
||||
modelFactory = SQLModelFactory(db)
|
||||
habitList = modelFactory.buildHabitList()
|
||||
fixtures = HabitFixtures(modelFactory, habitList)
|
||||
}
|
||||
|
||||
private fun migrateTo23() = helper.migrateTo(23)
|
||||
|
||||
@Test
|
||||
fun `test migrate to 23 creates question column`() {
|
||||
migrateTo23()
|
||||
val cursor = db.query("select question from Habits")
|
||||
cursor.moveToNext()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test migrate to 23 moves description to question column`() {
|
||||
var cursor = db.query("select description from Habits")
|
||||
|
||||
val descriptions = mutableListOf<String?>()
|
||||
while(cursor.moveToNext()){
|
||||
descriptions.add(cursor.getString(0))
|
||||
}
|
||||
|
||||
migrateTo23()
|
||||
cursor = db.query("select question from Habits")
|
||||
|
||||
for(i in 0 until descriptions.size){
|
||||
cursor.moveToNext()
|
||||
MatcherAssert.assertThat(cursor.getString(0), Matchers.equalTo(descriptions[i]))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test migrate to 23 sets description to null`() {
|
||||
migrateTo23()
|
||||
val cursor = db.query("select description from Habits")
|
||||
|
||||
while(cursor.moveToNext()){
|
||||
MatcherAssert.assertThat(cursor.getString(0), Matchers.equalTo(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,13 +218,15 @@ public class HabitListTest extends BaseUnitTest
|
||||
|
||||
Habit h1 = fixtures.createEmptyHabit();
|
||||
h1.setName("Meditate");
|
||||
h1.setDescription("Did you meditate this morning?");
|
||||
h1.setQuestion("Did you meditate this morning?");
|
||||
h1.setDescription("this is a test description");
|
||||
h1.setFrequency(Frequency.DAILY);
|
||||
h1.setColor(3);
|
||||
|
||||
Habit h2 = fixtures.createEmptyHabit();
|
||||
h2.setName("Wake up early");
|
||||
h2.setDescription("Did you wake up before 6am?");
|
||||
h2.setQuestion("Did you wake up before 6am?");
|
||||
h2.setDescription("");
|
||||
h2.setFrequency(new Frequency(2, 3));
|
||||
h2.setColor(5);
|
||||
|
||||
@@ -232,9 +234,9 @@ public class HabitListTest extends BaseUnitTest
|
||||
list.add(h2);
|
||||
|
||||
String expectedCSV =
|
||||
"Position,Name,Description,NumRepetitions,Interval,Color\n" +
|
||||
"001,Meditate,Did you meditate this morning?,1,1,#FF8F00\n" +
|
||||
"002,Wake up early,Did you wake up before 6am?,2,3,#AFB42B\n";
|
||||
"Position,Name,Question,Description,NumRepetitions,Interval,Color\n" +
|
||||
"001,Meditate,Did you meditate this morning?,this is a test description,1,1,#FF8F00\n" +
|
||||
"002,Wake up early,Did you wake up before 6am?,,2,3,#AFB42B\n";
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
list.writeCSV(writer);
|
||||
|
||||
@@ -155,7 +155,7 @@ public class HabitTest extends BaseUnitTest
|
||||
" targetValue: 100.0, type: 0, unit: ," +
|
||||
" reminder: {hour: 22, minute: 30," +
|
||||
" days: {weekdays: [true,true,true,true,true,true,true]}}," +
|
||||
" position: 0}}";
|
||||
" position: 0, question: }}";
|
||||
|
||||
assertThat(h.toString(), equalTo(expected));
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class HabitRecordTest extends BaseUnitTest
|
||||
{
|
||||
Habit original = modelFactory.buildHabit();
|
||||
original.setName("Hello world");
|
||||
original.setDescription("Did you greet the world today?");
|
||||
original.setQuestion("Did you greet the world today?");
|
||||
original.setColor(1);
|
||||
original.setArchived(true);
|
||||
original.setFrequency(Frequency.THREE_TIMES_PER_WEEK);
|
||||
@@ -58,7 +58,7 @@ public class HabitRecordTest extends BaseUnitTest
|
||||
{
|
||||
Habit original = modelFactory.buildHabit();
|
||||
original.setName("Hello world");
|
||||
original.setDescription("Did you greet the world today?");
|
||||
original.setQuestion("Did you greet the world today?");
|
||||
original.setColor(5);
|
||||
original.setArchived(false);
|
||||
original.setFrequency(Frequency.DAILY);
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.core.ui.screens.about;
|
||||
|
||||
import org.isoron.uhabits.core.*;
|
||||
import org.isoron.uhabits.core.preferences.*;
|
||||
import org.junit.*;
|
||||
import org.mockito.*;
|
||||
|
||||
import static org.isoron.uhabits.core.ui.screens.about.AboutBehavior.Message.YOU_ARE_NOW_A_DEVELOPER;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
public class AboutBehaviorTest extends BaseUnitTest
|
||||
{
|
||||
private AboutBehavior behavior;
|
||||
|
||||
@Mock
|
||||
private Preferences prefs;
|
||||
|
||||
@Mock
|
||||
private AboutBehavior.Screen screen;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
super.setUp();
|
||||
behavior = new AboutBehavior(prefs, screen);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPressDeveloperCountdown() throws Exception
|
||||
{
|
||||
behavior.onPressDeveloperCountdown();
|
||||
behavior.onPressDeveloperCountdown();
|
||||
behavior.onPressDeveloperCountdown();
|
||||
behavior.onPressDeveloperCountdown();
|
||||
verifyZeroInteractions(screen);
|
||||
verifyZeroInteractions(prefs);
|
||||
|
||||
behavior.onPressDeveloperCountdown();
|
||||
verify(screen).showMessage(YOU_ARE_NOW_A_DEVELOPER);
|
||||
verify(prefs).setDeveloper(true);
|
||||
|
||||
behavior.onPressDeveloperCountdown();
|
||||
verifyZeroInteractions(screen);
|
||||
verifyZeroInteractions(prefs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onRateApp() throws Exception
|
||||
{
|
||||
behavior.onRateApp();
|
||||
verify(screen).showRateAppWebsite();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSendFeedback() throws Exception
|
||||
{
|
||||
behavior.onSendFeedback();
|
||||
verify(screen).showSendFeedbackScreen();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTranslateApp() throws Exception
|
||||
{
|
||||
behavior.onTranslateApp();
|
||||
verify(screen).showTranslationWebsite();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onViewSourceCode() throws Exception
|
||||
{
|
||||
behavior.onViewSourceCode();
|
||||
verify(screen).showSourceCodeWebsite();
|
||||
}
|
||||
|
||||
}
|
||||
BIN
android/uhabits-core/src/test/resources/databases/022.db
Normal file
BIN
android/uhabits-core/src/test/resources/databases/022.db
Normal file
Binary file not shown.
Reference in New Issue
Block a user