Move existing source code to subfolder

This commit is contained in:
2019-01-22 13:19:54 -06:00
parent db1ba822fe
commit 4ccda9d6f7
882 changed files with 0 additions and 0 deletions

View File

@@ -1,232 +0,0 @@
<?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/>.
-->
<manifest
package="org.isoron.uhabits"
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="36"
android:versionName="1.7.9">
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:name=".HabitsApplication"
android:allowBackup="true"
android:backupAgent=".HabitsBackupAgent"
android:icon="@mipmap/ic_launcher"
android:label="@string/main_activity_title"
android:supportsRtl="true"
android:theme="@style/AppBaseTheme">
<meta-data
android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw"/>
<activity
android:name=".activities.habits.list.ListHabitsActivity"
android:exported="true"
android:label="@string/main_activity_title"
android:launchMode="singleTop"/>
<activity-alias
android:name=".MainActivity"
android:label="@string/main_activity_title"
android:launchMode="singleTop"
android:targetActivity=".activities.habits.list.ListHabitsActivity">
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
<activity
android:name=".activities.habits.show.ShowHabitActivity"
android:label="@string/title_activity_show_habit">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity"/>
</activity>
<activity
android:name=".activities.settings.SettingsActivity"
android:label="@string/settings">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity"/>
</activity>
<activity
android:name=".activities.intro.IntroActivity"
android:label=""
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
<activity
android:name=".widgets.HabitPickerDialog"
android:theme="@style/Theme.AppCompat.Light.Dialog">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
<activity
android:name=".activities.about.AboutActivity"
android:label="@string/about">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity"/>
</activity>
<activity android:name=".notifications.SnoozeDelayPickerActivity"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
</activity>
<receiver
android:name=".widgets.CheckmarkWidgetProvider"
android:label="@string/checkmark">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_checkmark_info"/>
</receiver>
<service android:name=".widgets.StackWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS"
android:exported="false" />
<receiver
android:name=".widgets.HistoryWidgetProvider"
android:label="@string/history">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_history_info"/>
</receiver>
<receiver
android:name=".widgets.ScoreWidgetProvider"
android:label="@string/habit_strength">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_score_info"/>
</receiver>
<receiver
android:name=".widgets.StreakWidgetProvider"
android:label="@string/streaks">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_streak_info"/>
</receiver>
<receiver
android:name=".widgets.FrequencyWidgetProvider"
android:label="@string/frequency">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_frequency_info"/>
</receiver>
<receiver android:name=".receivers.ReminderReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<receiver android:name=".receivers.WidgetReceiver">
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="org.isoron.uhabits.ACTION_TOGGLE_REPETITION"/>
<data
android:host="org.isoron.uhabits"
android:scheme="content"/>
</intent-filter>
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="org.isoron.uhabits.ACTION_ADD_REPETITION"/>
<data
android:host="org.isoron.uhabits"
android:scheme="content"/>
</intent-filter>
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="org.isoron.uhabits.ACTION_REMOVE_REPETITION"/>
<data
android:host="org.isoron.uhabits"
android:scheme="content"/>
</intent-filter>
</receiver>
<!-- Locale/Tasker -->
<activity
android:name=".automation.EditSettingActivity"
android:exported="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING"/>
</intent-filter>
</activity>
<!-- Locale/Tasker -->
<receiver
android:name=".automation.FireSettingReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/>
</intent-filter>
</receiver>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="org.isoron.uhabits"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
<service
android:name=".sync.SyncService"
android:enabled="true"
android:exported="false">
</service>
</application>
</manifest>

View File

@@ -1,41 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO
BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi
MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ
ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ
8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6
zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y
fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7
w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc
G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k
epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q
laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ
QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU
fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826
YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w
ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY
gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe
MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0
IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy
dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw
czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0
dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl
aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC
AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg
b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB
ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc
nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg
18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c
gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl
Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY
sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T
SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF
CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum
GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk
zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW
omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD
-----END CERTIFICATE-----

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -1,41 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits
import android.util.*
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.utils.*
import java.util.*
import javax.inject.*
@AppScope
class HabitLogger
@Inject constructor() {
fun logReminderScheduled(habit: Habit, reminderTime: Long) {
val min = Math.min(3, habit.name.length)
val name = habit.name.substring(0, min)
val df = DateFormats.getBackupDateFormat()
val time = df.format(Date(reminderTime))
Log.i("ReminderHelper",
String.format("Setting alarm (%s): %s", time, name))
}
}

View File

@@ -1,104 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits
import android.app.*
import android.content.*
import org.isoron.androidbase.*
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.core.reminders.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.widgets.*
import java.io.*
/**
* The Android application for Loop Habit Tracker.
*/
class HabitsApplication : Application() {
private lateinit var context: Context
private lateinit var widgetUpdater: WidgetUpdater
private lateinit var reminderScheduler: ReminderScheduler
private lateinit var notificationTray: NotificationTray
override fun onCreate() {
super.onCreate()
context = this
HabitsApplication.component = DaggerHabitsApplicationComponent
.builder()
.appContextModule(AppContextModule(context))
.build()
if (isTestMode()) {
val db = DatabaseUtils.getDatabaseFile(context)
if (db.exists()) db.delete()
}
try {
DatabaseUtils.initializeDatabase(context)
} catch (e: UnsupportedDatabaseVersionException) {
val db = DatabaseUtils.getDatabaseFile(context)
db.renameTo(File(db.absolutePath + ".invalid"))
DatabaseUtils.initializeDatabase(context)
}
widgetUpdater = component.widgetUpdater
widgetUpdater.startListening()
reminderScheduler = component.reminderScheduler
reminderScheduler.startListening()
notificationTray = component.notificationTray
notificationTray.startListening()
val prefs = component.preferences
prefs.setLastAppVersion(BuildConfig.VERSION_CODE)
val taskRunner = component.taskRunner
taskRunner.execute {
reminderScheduler.scheduleAll()
widgetUpdater.updateWidgets()
}
}
override fun onTerminate() {
reminderScheduler.stopListening()
widgetUpdater.stopListening()
notificationTray.stopListening()
super.onTerminate()
}
val component: HabitsApplicationComponent
get() = HabitsApplication.component
companion object {
lateinit var component: HabitsApplicationComponent
fun isTestMode(): Boolean {
try {
Class.forName("org.isoron.uhabits.BaseAndroidTest")
return true
} catch (e: ClassNotFoundException) {
return false
}
}
}
}

View File

@@ -1,93 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits;
import android.content.*;
import org.isoron.androidbase.*;
import org.isoron.uhabits.core.*;
import org.isoron.uhabits.core.commands.*;
import org.isoron.uhabits.core.io.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.reminders.*;
import org.isoron.uhabits.core.tasks.*;
import org.isoron.uhabits.core.ui.*;
import org.isoron.uhabits.core.ui.screens.habits.list.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.receivers.*;
import org.isoron.uhabits.sync.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.widgets.*;
import dagger.*;
@AppScope
@Component(modules = {
AppContextModule.class,
HabitsModule.class,
AndroidTaskRunner.class,
})
public interface HabitsApplicationComponent
{
CommandRunner getCommandRunner();
@AppContext
Context getContext();
CreateHabitCommandFactory getCreateHabitCommandFactory();
EditHabitCommandFactory getEditHabitCommandFactory();
GenericImporter getGenericImporter();
HabitCardListCache getHabitCardListCache();
HabitList getHabitList();
HabitLogger getHabitsLogger();
IntentFactory getIntentFactory();
IntentParser getIntentParser();
MidnightTimer getMidnightTimer();
ModelFactory getModelFactory();
NotificationTray getNotificationTray();
PendingIntentFactory getPendingIntentFactory();
Preferences getPreferences();
ReminderScheduler getReminderScheduler();
ReminderController getReminderController();
SyncManager getSyncManager();
TaskRunner getTaskRunner();
WidgetPreferences getWidgetPreferences();
WidgetUpdater getWidgetUpdater();
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits
import android.app.backup.*
/**
* An Android BackupAgentHelper customized for this application.
*/
class HabitsBackupAgent : BackupAgentHelper() {
override fun onCreate() {
addHelper("preferences", SharedPreferencesBackupHelper(this, "preferences"))
addHelper("database", FileBackupHelper(this, "../databases/uhabits.db"))
}
}

View File

@@ -1,54 +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
import android.content.*
import android.database.sqlite.*
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.database.*
class HabitsDatabaseOpener(
context: Context,
databaseFilename: String,
private val version: Int
) : SQLiteOpenHelper(context, databaseFilename, null, version) {
override fun onCreate(db: SQLiteDatabase) {
db.version = 8
onUpgrade(db, -1, version)
}
override fun onUpgrade(db: SQLiteDatabase,
oldVersion: Int,
newVersion: Int) {
if (db.version < 8) throw UnsupportedDatabaseVersionException()
val helper = MigrationHelper(AndroidDatabase(db))
helper.migrateTo(newVersion)
}
override fun onDowngrade(db: SQLiteDatabase,
oldVersion: Int,
newVersion: Int) {
throw UnsupportedDatabaseVersionException()
}
}

View File

@@ -1,93 +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
import dagger.*
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.sqlite.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.reminders.*
import org.isoron.uhabits.core.tasks.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.database.*
import org.isoron.uhabits.intents.*
import org.isoron.uhabits.notifications.*
import org.isoron.uhabits.preferences.*
import org.isoron.uhabits.utils.*
@Module
class HabitsModule {
@Provides
@AppScope
fun getPreferences(storage: SharedPreferencesStorage): Preferences {
return Preferences(storage)
}
@Provides
@AppScope
fun getReminderScheduler(
sys: IntentScheduler,
commandRunner: CommandRunner,
habitList: HabitList
): ReminderScheduler {
return ReminderScheduler(commandRunner, habitList, sys)
}
@Provides
@AppScope
fun getTray(
taskRunner: TaskRunner,
commandRunner: CommandRunner,
preferences: Preferences,
screen: AndroidNotificationTray
): NotificationTray {
return NotificationTray(taskRunner, commandRunner, preferences, screen)
}
@Provides
@AppScope
fun getWidgetPreferences(
storage: SharedPreferencesStorage
): WidgetPreferences {
return WidgetPreferences(storage)
}
@Provides
@AppScope
fun getModelFactory(): ModelFactory {
return SQLModelFactory(AndroidDatabase(DatabaseUtils.openDatabase()))
}
@Provides
@AppScope
fun getHabitList(list: SQLiteHabitList): HabitList {
return list
}
@Provides
@AppScope
fun getDatabaseOpener(opener: AndroidDatabaseOpener): DatabaseOpener {
return opener
}
}

View File

@@ -1,46 +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.activities
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.*
import javax.inject.*
@ActivityScope
class AndroidThemeSwitcher
@Inject constructor(
private val activity: BaseActivity,
preferences: Preferences
) : ThemeSwitcher(preferences) {
override fun applyDarkTheme() {
activity.setTheme(R.style.AppBaseThemeDark)
}
override fun applyLightTheme() {
activity.setTheme(R.style.AppBaseTheme)
}
override fun applyPureBlackTheme() {
activity.setTheme(R.style.AppBaseThemeDark_PureBlack)
}
}

View File

@@ -1,28 +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.activities
import dagger.*
import org.isoron.uhabits.core.models.*
@Module
class HabitModule(private val habit: Habit) {
@Provides fun getHabit() = habit
}

View File

@@ -1,57 +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.activities
import android.content.*
import android.os.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
abstract class HabitsActivity : BaseActivity() {
lateinit var component: HabitsActivityComponent
lateinit var appComponent: HabitsApplicationComponent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appComponent = (applicationContext as HabitsApplication).component
val habit = getHabitFromIntent(appComponent.habitList)
?: appComponent.modelFactory.buildHabit()
component = DaggerHabitsActivityComponent
.builder()
.activityContextModule(ActivityContextModule(this))
.baseActivityModule(BaseActivityModule(this))
.habitModule(HabitModule(habit))
.habitsApplicationComponent(appComponent)
.build()
component.themeSwitcher.apply()
}
private fun getHabitFromIntent(habitList: HabitList): Habit? {
val data = intent.data ?: return null
val habit = habitList.getById(ContentUris.parseId(data))
?: throw RuntimeException("habit not found")
return habit
}
}

View File

@@ -1,55 +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.activities
import dagger.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.about.*
import org.isoron.uhabits.activities.common.dialogs.*
import org.isoron.uhabits.activities.habits.list.*
import org.isoron.uhabits.activities.habits.list.views.*
import org.isoron.uhabits.activities.habits.show.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
@ActivityScope
@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
val listHabitsMenu: ListHabitsMenu
val listHabitsRootView: ListHabitsRootView
val listHabitsScreen: ListHabitsScreen
val listHabitsSelectionMenu: ListHabitsSelectionMenu
val showHabitScreen: ShowHabitScreen
val themeSwitcher: ThemeSwitcher
}

View File

@@ -1,30 +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.activities
import dagger.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.core.ui.*
@Module
abstract class HabitsActivityModule {
@Binds @ActivityScope
internal abstract fun getThemeSwitcher(t: AndroidThemeSwitcher): ThemeSwitcher
}

View File

@@ -1,35 +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.activities
import org.isoron.androidbase.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.core.ui.screens.habits.show.*
import java.io.*
import javax.inject.*
class HabitsDirFinder @Inject
constructor(
private val androidDirFinder: AndroidDirFinder
) : ShowHabitMenuBehavior.System, ListHabitsBehavior.DirFinder {
override fun getCSVOutputDir(): File {
return androidDirFinder.getFilesDir("CSV")!!
}
}

View File

@@ -1,40 +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.activities.about;
import android.os.*;
import org.isoron.uhabits.activities.*;
/**
* Activity that allows the user to see information about the app itself.
* Display current version, link to Google Play and list of contributors.
*/
public class AboutActivity extends HabitsActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
AboutScreen screen = getComponent().getAboutScreen();
screen.setRootView(getComponent().getAboutRootView());
setScreen(screen);
}
}

View File

@@ -1,30 +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.activities.about;
import org.isoron.uhabits.core.ui.screens.about.*;
import dagger.*;
@Module
public abstract class AboutModule
{
@Binds
abstract AboutBehavior.Screen getScreen(AboutScreen screen);
}

View File

@@ -1,106 +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.activities.about;
import android.content.*;
import android.support.annotation.*;
import android.widget.*;
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.*;
import butterknife.*;
public class AboutRootView extends BaseRootView
{
@BindView(R.id.tvVersion)
TextView tvVersion;
@NonNull
private final AboutBehavior behavior;
@Inject
public AboutRootView(@NonNull @ActivityContext Context context,
@NonNull AboutBehavior behavior)
{
super(context);
this.behavior = behavior;
addView(inflate(getContext(), R.layout.about, null));
ButterKnife.bind(this);
String version = getResources().getString(R.string.version_n);
tvVersion.setText(String.format(version, BuildConfig.VERSION_NAME));
setDisplayHomeAsUp(true);
}
@Override
public int getToolbarColor()
{
StyledResources res = new StyledResources(getContext());
if (!res.getBoolean(R.attr.useHabitColorAsPrimary))
return super.getToolbarColor();
return res.getColor(R.attr.aboutScreenColor);
}
@OnClick(R.id.tvFeedback)
public void onClickFeedback()
{
behavior.onSendFeedback();
}
@OnClick(R.id.tvVersion)
public void onClickIcon()
{
behavior.onPressDeveloperCountdown();
}
@OnClick(R.id.tvRate)
public void onClickRate()
{
behavior.onRateApp();
}
@OnClick(R.id.tvSource)
public void onClickSource()
{
behavior.onViewSourceCode();
}
@OnClick(R.id.tvTranslate)
public void onClickTranslate()
{
behavior.onTranslateApp();
}
@Override
protected void initToolbar()
{
super.initToolbar();
getToolbar().setTitle(getResources().getString(R.string.about));
}
}

View File

@@ -1,77 +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.activities.about;
import android.support.annotation.*;
import android.widget.*;
import org.isoron.androidbase.activities.*;
import org.isoron.uhabits.core.ui.screens.about.*;
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
{
@NonNull
private final IntentFactory intents;
@Inject
public AboutScreen(@NonNull BaseActivity activity,
@NonNull IntentFactory intents)
{
super(activity);
this.intents = intents;
}
@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));
}
}

View File

@@ -1,38 +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.activities.common.dialogs;
import org.isoron.uhabits.core.ui.callbacks.*;
import org.isoron.uhabits.utils.*;
/**
* Dialog that allows the user to choose a color.
*/
public class ColorPickerDialog extends com.android.colorpicker.ColorPickerDialog
{
public void setListener(OnColorPickedCallback callback)
{
super.setOnColorSelectedListener(c ->
{
c = PaletteUtils.colorToPaletteIndex(getContext(), c);
callback.onColorPicked(c);
});
}
}

View File

@@ -1,53 +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.activities.common.dialogs;
import android.content.*;
import org.isoron.androidbase.activities.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.utils.*;
import javax.inject.*;
@ActivityScope
public class ColorPickerDialogFactory
{
private final Context context;
@Inject
public ColorPickerDialogFactory(@ActivityContext Context context)
{
this.context = context;
}
public ColorPickerDialog create(int paletteColor)
{
ColorPickerDialog dialog = new ColorPickerDialog();
StyledResources res = new StyledResources(context);
int color = PaletteUtils.getColor(context, paletteColor);
dialog.initialize(R.string.color_picker_default_title, res.getPalette(),
color, 4, com.android.colorpicker.ColorPickerDialog.SIZE_SMALL);
return dialog;
}
}

View File

@@ -1,60 +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.activities.common.dialogs;
import android.content.*;
import android.support.annotation.*;
import android.support.v7.app.*;
import com.google.auto.factory.*;
import org.isoron.androidbase.activities.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.core.ui.callbacks.*;
import butterknife.*;
/**
* Dialog that asks the user confirmation before executing a delete operation.
*/
@AutoFactory(allowSubclasses = true)
public class ConfirmDeleteDialog extends AlertDialog
{
@BindString(R.string.delete_habits_message)
protected String question;
@BindString(android.R.string.yes)
protected String yes;
@BindString(android.R.string.no)
protected String no;
protected ConfirmDeleteDialog(@Provided @ActivityContext Context context,
@NonNull OnConfirmedCallback callback)
{
super(context);
ButterKnife.bind(this);
setTitle(R.string.delete_habits);
setMessage(question);
setButton(BUTTON_POSITIVE, yes, (dialog, which) -> callback.onConfirmed());
setButton(BUTTON_NEGATIVE, no, (dialog, which) -> {});
}
}

View File

@@ -1,179 +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.activities.common.dialogs;
import android.app.*;
import android.content.*;
import android.os.*;
import android.support.annotation.*;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.*;
import android.util.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.tasks.*;
import org.isoron.uhabits.utils.*;
import static org.isoron.androidbase.utils.InterfaceUtils.*;
public class HistoryEditorDialog extends AppCompatDialogFragment
implements DialogInterface.OnClickListener, ModelObservable.Listener
{
@Nullable
private Habit habit;
@Nullable
HistoryChart historyChart;
@NonNull
private Controller controller;
private HabitList habitList;
private TaskRunner taskRunner;
public HistoryEditorDialog()
{
this.controller = new Controller() {};
}
@Override
public void onClick(DialogInterface dialog, int which)
{
dismiss();
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState)
{
Context context = getActivity();
HabitsApplication app =
(HabitsApplication) getActivity().getApplicationContext();
habitList = app.getComponent().getHabitList();
taskRunner = app.getComponent().getTaskRunner();
historyChart = new HistoryChart(context);
historyChart.setController(controller);
if (savedInstanceState != null)
{
long id = savedInstanceState.getLong("habit", -1);
if (id > 0) this.habit = habitList.getById(id);
historyChart.onRestoreInstanceState(
savedInstanceState.getParcelable("historyChart"));
}
int padding =
(int) getDimension(getContext(), R.dimen.history_editor_padding);
historyChart.setPadding(padding, 0, padding, 0);
historyChart.setIsEditable(true);
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder
.setTitle(R.string.history)
.setView(historyChart)
.setPositiveButton(android.R.string.ok, this);
return builder.create();
}
@Override
public void onModelChange()
{
refreshData();
}
@Override
public void onPause()
{
habit.getCheckmarks().observable.removeListener(this);
super.onPause();
}
@Override
public void onResume()
{
super.onResume();
DisplayMetrics metrics = getResources().getDisplayMetrics();
int maxHeight = getResources().getDimensionPixelSize(
R.dimen.history_editor_max_height);
int width = metrics.widthPixels;
int height = Math.min(metrics.heightPixels, maxHeight);
getDialog().getWindow().setLayout(width, height);
refreshData();
habit.getCheckmarks().observable.addListener(this);
}
@Override
public void onSaveInstanceState(Bundle outState)
{
outState.putLong("habit", habit.getId());
outState.putParcelable("historyChart", historyChart.onSaveInstanceState());
}
public void setController(@NonNull Controller controller)
{
this.controller = controller;
if (historyChart != null) historyChart.setController(controller);
}
public void setHabit(@Nullable Habit habit)
{
this.habit = habit;
}
private void refreshData()
{
if (habit == null) return;
taskRunner.execute(new RefreshTask());
}
public interface Controller extends HistoryChart.Controller {}
private class RefreshTask implements Task
{
public int[] checkmarks;
@Override
public void doInBackground()
{
checkmarks = habit.getCheckmarks().getAllValues();
}
@Override
public void onPostExecute()
{
if (getContext() == null || habit == null || historyChart == null)
return;
int color = PaletteUtils.getColor(getContext(), habit.getColor());
historyChart.setColor(color);
historyChart.setCheckmarks(checkmarks);
}
}
}

View File

@@ -1,93 +0,0 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
package org.isoron.uhabits.activities.common.dialogs
import android.content.*
import android.support.v7.app.*
import android.text.*
import android.view.*
import android.view.inputmethod.*
import android.widget.*
import org.isoron.androidbase.activities.*
import org.isoron.androidbase.utils.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import javax.inject.*
class NumberPickerFactory
@Inject constructor(
@ActivityContext private val context: Context
) {
fun create(value: Double,
unit: String,
callback: ListHabitsBehavior.NumberPickerCallback): AlertDialog {
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.number_picker_dialog, null)
val picker = view.findViewById<NumberPicker>(R.id.picker)
val picker2 = view.findViewById<NumberPicker>(R.id.picker2)
val tvUnit = view.findViewById<TextView>(R.id.tvUnit)
val intValue = Math.round(value * 100).toInt()
picker.minValue = 0
picker.maxValue = Integer.MAX_VALUE / 100
picker.value = intValue / 100
picker.wrapSelectorWheel = false
picker2.minValue = 0
picker2.maxValue = 19
picker2.setFormatter { v -> String.format("%02d", 5 * v) }
picker2.value = intValue % 100 / 5
refreshInitialValue(picker2)
tvUnit.text = unit
val dialog = AlertDialog.Builder(context)
.setView(view)
.setTitle(R.string.change_value)
.setPositiveButton(android.R.string.ok) { _, _ ->
picker.clearFocus()
val v = picker.value + 0.05 * picker2.value
callback.onNumberPicked(v)
}
.create()
dialog.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
InterfaceUtils.setupEditorAction(picker) { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE)
dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
false
}
return dialog
}
private fun refreshInitialValue(picker: NumberPicker) {
// Workaround for Android bug:
// https://code.google.com/p/android/issues/detail?id=35482
val f = NumberPicker::class.java.getDeclaredField("mInputText")
f.isAccessible = true
val inputText = f.get(picker) as EditText
inputText.filters = arrayOfNulls<InputFilter>(0)
}
}

View File

@@ -1,86 +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.activities.common.dialogs;
import android.app.*;
import android.content.*;
import android.os.*;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.utils.*;
/**
* Dialog that allows the user to pick one or more days of the week.
*/
public class WeekdayPickerDialog extends AppCompatDialogFragment implements
DialogInterface.OnMultiChoiceClickListener,
DialogInterface.OnClickListener
{
private boolean[] selectedDays;
private OnWeekdaysPickedListener listener;
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked)
{
selectedDays[which] = isChecked;
}
@Override
public void onClick(DialogInterface dialog, int which)
{
if (listener != null)
listener.onWeekdaysSet(new WeekdayList(selectedDays));
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder
.setTitle(R.string.select_weekdays)
.setMultiChoiceItems(DateUtils.getLongDayNames(), selectedDays,
this)
.setPositiveButton(android.R.string.yes, this)
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
dismiss();
});
return builder.create();
}
public void setListener(OnWeekdaysPickedListener listener)
{
this.listener = listener;
}
public void setSelectedDays(WeekdayList days)
{
this.selectedDays = days.toArray();
}
public interface OnWeekdaysPickedListener
{
void onWeekdaysSet(WeekdayList days);
}
}

View File

@@ -1,479 +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.activities.common.views;
import android.content.*;
import android.graphics.*;
import android.support.annotation.*;
import android.util.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.habits.list.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.utils.*;
import java.text.*;
import java.util.*;
import static org.isoron.androidbase.utils.InterfaceUtils.*;
public class BarChart extends ScrollableChart
{
private static final PorterDuffXfermode XFERMODE_CLEAR =
new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
private static final PorterDuffXfermode XFERMODE_SRC =
new PorterDuffXfermode(PorterDuff.Mode.SRC);
private Paint pGrid;
private float em;
private SimpleDateFormat dfMonth;
private SimpleDateFormat dfDay;
private SimpleDateFormat dfYear;
private Paint pText, pGraph;
private RectF rect, prevRect;
private int baseSize;
private int paddingTop;
private float columnWidth;
private int columnHeight;
private int nColumns;
private int textColor;
private int gridColor;
@Nullable
private List<Checkmark> checkmarks;
private int primaryColor;
private int bucketSize = 7;
private int backgroundColor;
private Bitmap drawingCache;
private Canvas cacheCanvas;
private boolean isTransparencyEnabled;
private int skipYear = 0;
private String previousYearText;
private String previousMonthText;
private double maxValue;
private double target;
public BarChart(Context context)
{
super(context);
init();
}
public BarChart(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
public void populateWithRandomData()
{
Random random = new Random();
List<Checkmark> checkmarks = new LinkedList<>();
Timestamp today = DateUtils.getToday();
for (int i = 1; i < 100; i++)
{
int value = random.nextInt(1000);
checkmarks.add(new Checkmark(today.minus(i), value));
}
setCheckmarks(checkmarks);
setTarget(0.5);
}
public void setBucketSize(int bucketSize)
{
this.bucketSize = bucketSize;
postInvalidate();
}
public void setCheckmarks(@NonNull List<Checkmark> checkmarks)
{
this.checkmarks = checkmarks;
maxValue = 1.0;
for (Checkmark c : checkmarks)
maxValue = Math.max(maxValue, c.getValue());
maxValue = Math.ceil(maxValue / 1000 * 1.05) * 1000;
postInvalidate();
}
public void setColor(int primaryColor)
{
this.primaryColor = primaryColor;
postInvalidate();
}
public void setIsTransparencyEnabled(boolean enabled)
{
this.isTransparencyEnabled = enabled;
postInvalidate();
}
public void setTarget(double target)
{
this.target = target;
postInvalidate();
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Canvas activeCanvas;
if (isTransparencyEnabled)
{
if (drawingCache == null) initCache(getWidth(), getHeight());
activeCanvas = cacheCanvas;
drawingCache.eraseColor(Color.TRANSPARENT);
}
else
{
activeCanvas = canvas;
}
if (checkmarks == null) return;
rect.set(0, 0, nColumns * columnWidth, columnHeight);
rect.offset(0, paddingTop);
drawGrid(activeCanvas, rect);
pText.setColor(textColor);
pGraph.setColor(primaryColor);
prevRect.setEmpty();
previousMonthText = "";
previousYearText = "";
skipYear = 0;
for (int k = 0; k < nColumns; k++)
{
int offset = nColumns - k - 1 + getDataOffset();
if (offset >= checkmarks.size()) continue;
double value = checkmarks.get(offset).getValue();
Timestamp timestamp = checkmarks.get(offset).getTimestamp();
int height = (int) (columnHeight * value / maxValue);
rect.set(0, 0, baseSize, height);
rect.offset(k * columnWidth + (columnWidth - baseSize) / 2,
paddingTop + columnHeight - height);
drawValue(activeCanvas, rect, value);
drawBar(activeCanvas, rect, value);
prevRect.set(rect);
rect.set(0, 0, columnWidth, columnHeight);
rect.offset(k * columnWidth, paddingTop);
drawFooter(activeCanvas, rect, timestamp);
}
if (activeCanvas != canvas) canvas.drawBitmap(drawingCache, 0, 0, null);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
@Override
protected void onSizeChanged(int width,
int height,
int oldWidth,
int oldHeight)
{
if (height < 9) height = 200;
float maxTextSize = getDimension(getContext(), R.dimen.tinyTextSize);
float textSize = height * 0.06f;
pText.setTextSize(Math.min(textSize, maxTextSize));
em = pText.getFontSpacing();
int footerHeight = (int) (3 * em);
paddingTop = (int) (em);
baseSize = (height - footerHeight - paddingTop) / 12;
columnWidth = baseSize;
columnWidth = Math.max(columnWidth, getMaxDayWidth() * 1.5f);
columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f);
nColumns = (int) (width / columnWidth);
columnWidth = (float) width / nColumns;
setScrollerBucketSize((int) columnWidth);
columnHeight = 12 * baseSize;
float minStrokeWidth = dpToPixels(getContext(), 1);
pGraph.setTextSize(baseSize * 0.5f);
pGraph.setStrokeWidth(baseSize * 0.1f);
pGrid.setStrokeWidth(Math.min(minStrokeWidth, baseSize * 0.05f));
if (isTransparencyEnabled) initCache(width, height);
}
private void drawBar(Canvas canvas, RectF rect, double value)
{
float margin = baseSize * 0.225f;
int color = textColor;
if (value / 1000 >= target) color = primaryColor;
rect.inset(-margin, 0);
setModeOrColor(pGraph, XFERMODE_CLEAR, backgroundColor);
canvas.drawRect(rect, pGraph);
rect.inset(margin, 0);
setModeOrColor(pGraph, XFERMODE_SRC, color);
canvas.drawRect(rect, pGraph);
if (isTransparencyEnabled) pGraph.setXfermode(XFERMODE_SRC);
}
private void drawFooter(Canvas canvas, RectF rect, Timestamp currentDate)
{
String yearText = dfYear.format(currentDate.toJavaDate());
String monthText = dfMonth.format(currentDate.toJavaDate());
String dayText = dfDay.format(currentDate.toJavaDate());
GregorianCalendar calendar = currentDate.toCalendar();
pText.setColor(textColor);
String text;
int year = calendar.get(Calendar.YEAR);
boolean shouldPrintYear = true;
if (yearText.equals(previousYearText)) shouldPrintYear = false;
if (skipYear > 0)
{
skipYear--;
shouldPrintYear = false;
}
if (bucketSize >= 365) shouldPrintYear = true;
if (shouldPrintYear)
{
previousYearText = yearText;
previousMonthText = "";
pText.setTextAlign(Paint.Align.CENTER);
canvas.drawText(yearText, rect.centerX(), rect.bottom + em * 2.2f, pText);
skipYear = 1;
}
if (bucketSize < 365)
{
if (!monthText.equals(previousMonthText))
{
previousMonthText = monthText;
text = monthText;
}
else
{
text = dayText;
}
canvas.drawText(text, rect.centerX(), rect.bottom + em * 1.2f,
pText);
}
}
private void drawGrid(Canvas canvas, RectF rGrid)
{
int nRows = 5;
float rowHeight = rGrid.height() / nRows;
pText.setColor(textColor);
pGrid.setColor(gridColor);
for (int i = 0; i < nRows; i++)
{
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top,
pGrid);
rGrid.offset(0, rowHeight);
}
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid);
}
private void drawValue(Canvas canvas, RectF rect, double value)
{
if (value == 0) return;
int activeColor = textColor;
if (value / 1000 >= target)
activeColor = primaryColor;
String label = NumberButtonViewKt.toShortString(value / 1000);
Rect rText = new Rect();
pText.getTextBounds(label, 0, label.length(), rText);
float offset = 0.5f * em;
float x = rect.centerX();
float y = rect.top - offset;
int cap = (int) (-0.1f * em);
rText.offset((int) x, (int) y);
rText.offset(-rText.width() / 2, 0);
rText.inset(3 * cap, cap);
setModeOrColor(pText, XFERMODE_CLEAR, backgroundColor);
canvas.drawRect(rText, pText);
setModeOrColor(pText, XFERMODE_SRC, activeColor);
canvas.drawText(label, x, y, pText);
}
private float getMaxDayWidth()
{
float maxDayWidth = 0;
GregorianCalendar day = DateUtils.getStartOfTodayCalendar();
for (int i = 0; i < 28; i++)
{
day.set(Calendar.DAY_OF_MONTH, i);
float monthWidth = pText.measureText(dfMonth.format(day.getTime()));
maxDayWidth = Math.max(maxDayWidth, monthWidth);
}
return maxDayWidth;
}
private float getMaxMonthWidth()
{
float maxMonthWidth = 0;
GregorianCalendar day = DateUtils.getStartOfTodayCalendar();
for (int i = 0; i < 12; i++)
{
day.set(Calendar.MONTH, i);
float monthWidth = pText.measureText(dfMonth.format(day.getTime()));
maxMonthWidth = Math.max(maxMonthWidth, monthWidth);
}
return maxMonthWidth;
}
private void init()
{
initPaints();
initColors();
initDateFormats();
initRects();
}
private void initCache(int width, int height)
{
if (drawingCache != null) drawingCache.recycle();
drawingCache =
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
cacheCanvas = new Canvas(drawingCache);
}
private void initColors()
{
StyledResources res = new StyledResources(getContext());
primaryColor = Color.BLACK;
textColor = res.getColor(R.attr.mediumContrastTextColor);
gridColor = res.getColor(R.attr.lowContrastTextColor);
backgroundColor = res.getColor(R.attr.cardBackgroundColor);
}
private void initDateFormats()
{
if (isInEditMode())
{
dfYear = new SimpleDateFormat("yyyy", Locale.US);
dfMonth = new SimpleDateFormat("MMM", Locale.US);
dfDay = new SimpleDateFormat("d", Locale.US);
return;
}
dfYear = AndroidDateFormats.fromSkeleton("yyyy");
dfMonth = AndroidDateFormats.fromSkeleton("MMM");
dfDay = AndroidDateFormats.fromSkeleton("d");
}
private void initPaints()
{
pText = new Paint();
pText.setAntiAlias(true);
pText.setTextAlign(Paint.Align.CENTER);
pGraph = new Paint();
pGraph.setTextAlign(Paint.Align.CENTER);
pGraph.setAntiAlias(true);
pGrid = new Paint();
pGrid.setAntiAlias(true);
}
private void initRects()
{
rect = new RectF();
prevRect = new RectF();
}
private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color)
{
if (isTransparencyEnabled) p.setXfermode(mode);
else p.setColor(color);
}
}

View File

@@ -1,65 +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.activities.common.views;
import android.os.*;
import android.support.v4.os.*;
public class BundleSavedState extends android.support.v4.view.AbsSavedState
{
public static final Parcelable.Creator<BundleSavedState> CREATOR =
ParcelableCompat.newCreator(
new ParcelableCompatCreatorCallbacks<BundleSavedState>()
{
@Override
public BundleSavedState createFromParcel(Parcel source,
ClassLoader loader)
{
return new BundleSavedState(source, loader);
}
@Override
public BundleSavedState[] newArray(int size)
{
return new BundleSavedState[size];
}
});
public final Bundle bundle;
public BundleSavedState(Parcelable superState, Bundle bundle)
{
super(superState);
this.bundle = bundle;
}
public BundleSavedState(Parcel source, ClassLoader loader)
{
super(source, loader);
this.bundle = source.readBundle(loader);
}
@Override
public void writeToParcel(Parcel out, int flags)
{
super.writeToParcel(out, flags);
out.writeBundle(bundle);
}
}

View File

@@ -1,339 +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.activities.common.views;
import android.content.*;
import android.graphics.*;
import android.support.annotation.*;
import android.util.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.utils.*;
import java.text.*;
import java.util.*;
public class FrequencyChart extends ScrollableChart
{
private Paint pGrid;
private float em;
private SimpleDateFormat dfMonth;
private SimpleDateFormat dfYear;
private Paint pText, pGraph;
private RectF rect, prevRect;
private int baseSize;
private int paddingTop;
private float columnWidth;
private int columnHeight;
private int nColumns;
private int textColor;
private int gridColor;
private int[] colors;
private int primaryColor;
private boolean isBackgroundTransparent;
@NonNull
private HashMap<Timestamp, Integer[]> frequency;
private int maxFreq;
public FrequencyChart(Context context)
{
super(context);
init();
}
public FrequencyChart(Context context, AttributeSet attrs)
{
super(context, attrs);
this.frequency = new HashMap<>();
init();
}
public void setColor(int color)
{
this.primaryColor = color;
initColors();
postInvalidate();
}
public void setFrequency(HashMap<Timestamp, Integer[]> frequency)
{
this.frequency = frequency;
maxFreq = getMaxFreq(frequency);
postInvalidate();
}
private int getMaxFreq(HashMap<Timestamp, Integer[]> frequency)
{
int maxValue = 1;
for (Integer[] values : frequency.values())
for (Integer value : values)
maxValue = Math.max(value, maxValue);
return maxValue;
}
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
{
this.isBackgroundTransparent = isBackgroundTransparent;
initColors();
}
protected void initPaints()
{
pText = new Paint();
pText.setAntiAlias(true);
pGraph = new Paint();
pGraph.setTextAlign(Paint.Align.CENTER);
pGraph.setAntiAlias(true);
pGrid = new Paint();
pGrid.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
rect.set(0, 0, nColumns * columnWidth, columnHeight);
rect.offset(0, paddingTop);
drawGrid(canvas, rect);
pText.setTextAlign(Paint.Align.CENTER);
pText.setColor(textColor);
pGraph.setColor(primaryColor);
prevRect.setEmpty();
GregorianCalendar currentDate = DateUtils.getStartOfTodayCalendar();
currentDate.set(Calendar.DAY_OF_MONTH, 1);
currentDate.add(Calendar.MONTH, -nColumns + 2 - getDataOffset());
for (int i = 0; i < nColumns - 1; i++)
{
rect.set(0, 0, columnWidth, columnHeight);
rect.offset(i * columnWidth, 0);
drawColumn(canvas, rect, currentDate);
currentDate.add(Calendar.MONTH, 1);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
@Override
protected void onSizeChanged(int width,
int height,
int oldWidth,
int oldHeight)
{
if (height < 9) height = 200;
baseSize = height / 8;
setScrollerBucketSize(baseSize);
pText.setTextSize(baseSize * 0.4f);
pGraph.setTextSize(baseSize * 0.4f);
pGraph.setStrokeWidth(baseSize * 0.1f);
pGrid.setStrokeWidth(baseSize * 0.05f);
em = pText.getFontSpacing();
columnWidth = baseSize;
columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f);
columnHeight = 8 * baseSize;
nColumns = (int) (width / columnWidth);
paddingTop = 0;
}
private void drawColumn(Canvas canvas, RectF rect, GregorianCalendar date)
{
Integer values[] = frequency.get(new Timestamp(date));
float rowHeight = rect.height() / 8.0f;
prevRect.set(rect);
Integer[] localeWeekdayList = DateUtils.getLocaleWeekdayList();
for (int j = 0; j < localeWeekdayList.length; j++)
{
rect.set(0, 0, baseSize, baseSize);
rect.offset(prevRect.left, prevRect.top + baseSize * j);
int i = localeWeekdayList[j] % 7;
if (values != null) drawMarker(canvas, rect, values[i]);
rect.offset(0, rowHeight);
}
drawFooter(canvas, rect, date);
}
private void drawFooter(Canvas canvas, RectF rect, GregorianCalendar date)
{
Date time = date.getTime();
canvas.drawText(dfMonth.format(time), rect.centerX(),
rect.centerY() - 0.1f * em, pText);
if (date.get(Calendar.MONTH) == 1)
canvas.drawText(dfYear.format(time), rect.centerX(),
rect.centerY() + 0.9f * em, pText);
}
private void drawGrid(Canvas canvas, RectF rGrid)
{
int nRows = 7;
float rowHeight = rGrid.height() / (nRows + 1);
pText.setTextAlign(Paint.Align.LEFT);
pText.setColor(textColor);
pGrid.setColor(gridColor);
for (String day : DateUtils.getLocaleDayNames(Calendar.SHORT))
{
canvas.drawText(day, rGrid.right - columnWidth,
rGrid.top + rowHeight / 2 + 0.25f * em, pText);
pGrid.setStrokeWidth(1f);
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top,
pGrid);
rGrid.offset(0, rowHeight);
}
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid);
}
private void drawMarker(Canvas canvas, RectF rect, Integer value)
{
float padding = rect.height() * 0.2f;
// maximal allowed mark radius
float maxRadius = (rect.height() - 2 * padding) / 2.0f;
// the real mark radius is scaled down by a factor depending on the maximal frequency
float scale = 1.0f/maxFreq * value;
float radius = maxRadius * scale;
int colorIndex = Math.min(colors.length - 1, Math.round((colors.length - 1) * scale));
pGraph.setColor(colors[colorIndex]);
canvas.drawCircle(rect.centerX(), rect.centerY(), radius, pGraph);
}
private float getMaxMonthWidth()
{
float maxMonthWidth = 0;
GregorianCalendar day = DateUtils.getStartOfTodayCalendar();
for (int i = 0; i < 12; i++)
{
day.set(Calendar.MONTH, i);
float monthWidth = pText.measureText(dfMonth.format(day.getTime()));
maxMonthWidth = Math.max(maxMonthWidth, monthWidth);
}
return maxMonthWidth;
}
private void init()
{
initPaints();
initColors();
initDateFormats();
initRects();
}
private void initColors()
{
StyledResources res = new StyledResources(getContext());
textColor = res.getColor(R.attr.mediumContrastTextColor);
gridColor = res.getColor(R.attr.lowContrastTextColor);
colors = new int[4];
colors[0] = gridColor;
colors[3] = primaryColor;
colors[1] = ColorUtils.mixColors(colors[0], colors[3], 0.66f);
colors[2] = ColorUtils.mixColors(colors[0], colors[3], 0.33f);
}
private void initDateFormats()
{
if (isInEditMode())
{
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
dfYear = new SimpleDateFormat("yyyy", Locale.getDefault());
}
else
{
dfMonth = AndroidDateFormats.fromSkeleton("MMM");
dfYear = AndroidDateFormats.fromSkeleton("yyyy");
}
}
private void initRects()
{
rect = new RectF();
prevRect = new RectF();
}
public void populateWithRandomData()
{
GregorianCalendar date = DateUtils.getStartOfTodayCalendar();
date.set(Calendar.DAY_OF_MONTH, 1);
Random rand = new Random();
frequency.clear();
for (int i = 0; i < 40; i++)
{
Integer values[] = new Integer[7];
for (int j = 0; j < 7; j++)
values[j] = rand.nextInt(5);
frequency.put(new Timestamp(date), values);
date.add(Calendar.MONTH, -1);
}
maxFreq = getMaxFreq(frequency);
}
}

View File

@@ -1,29 +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.activities.common.views;
import org.isoron.uhabits.core.models.Habit;
public interface HabitChart
{
void setHabit(Habit habit);
void refreshData();
}

View File

@@ -1,486 +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.activities.common.views;
import android.content.*;
import android.graphics.*;
import android.graphics.Paint.*;
import android.support.annotation.*;
import android.util.*;
import android.view.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.utils.*;
import java.text.*;
import java.util.*;
import static org.isoron.androidbase.utils.InterfaceUtils.*;
import static org.isoron.uhabits.core.models.Checkmark.*;
public class HistoryChart extends ScrollableChart
{
private int[] checkmarks;
private int target;
private Paint pSquareBg, pSquareFg, pTextHeader;
private float squareSpacing;
private float squareTextOffset;
private float headerTextOffset;
private float columnWidth;
private float columnHeight;
private int nColumns;
private SimpleDateFormat dfMonth;
private SimpleDateFormat dfYear;
private Calendar baseDate;
private int nDays;
/**
* 0-based-position of today in the column
*/
private int todayPositionInColumn;
private int colors[];
private RectF baseLocation;
private int primaryColor;
private boolean isBackgroundTransparent;
private int textColor;
private int reverseTextColor;
private boolean isEditable;
private String previousMonth;
private String previousYear;
private float headerOverflow = 0;
private boolean isNumerical = false;
@NonNull
private Controller controller;
public HistoryChart(Context context)
{
super(context);
init();
}
public HistoryChart(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
@Override
public void onLongPress(MotionEvent e)
{
onSingleTapUp(e);
}
@Override
public boolean onSingleTapUp(MotionEvent e)
{
if (!isEditable) return false;
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
float x, y;
try
{
int pointerId = e.getPointerId(0);
x = e.getX(pointerId);
y = e.getY(pointerId);
}
catch (RuntimeException ex)
{
// Android often throws IllegalArgumentException here. Apparently,
// the pointer id may become invalid shortly after calling
// e.getPointerId.
return false;
}
final Timestamp timestamp = positionToTimestamp(x, y);
if (timestamp == null) return false;
Timestamp today = DateUtils.getToday();
int offset = timestamp.daysUntil(today);
if (offset < checkmarks.length)
{
boolean isChecked = checkmarks[offset] == CHECKED_EXPLICITLY;
checkmarks[offset] = (isChecked ? UNCHECKED : CHECKED_EXPLICITLY);
}
controller.onToggleCheckmark(timestamp);
postInvalidate();
return true;
}
public void populateWithRandomData()
{
Random random = new Random();
checkmarks = new int[100];
for (int i = 0; i < 100; i++)
if (random.nextFloat() < 0.3) checkmarks[i] = 2;
for (int i = 0; i < 100 - 7; i++)
{
int count = 0;
for (int j = 0; j < 7; j++)
if (checkmarks[i + j] != 0) count++;
if (count >= 3) checkmarks[i] = Math.max(checkmarks[i], 1);
}
}
public void setCheckmarks(int[] checkmarks)
{
this.checkmarks = checkmarks;
postInvalidate();
}
public void setColor(int color)
{
this.primaryColor = color;
initColors();
postInvalidate();
}
public void setController(@NonNull Controller controller)
{
this.controller = controller;
}
public void setNumerical(boolean numerical)
{
isNumerical = numerical;
}
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
{
this.isBackgroundTransparent = isBackgroundTransparent;
initColors();
}
public void setIsEditable(boolean isEditable)
{
this.isEditable = isEditable;
}
public void setTarget(int target)
{
this.target = target;
postInvalidate();
}
protected void initPaints()
{
pTextHeader = new Paint();
pTextHeader.setTextAlign(Align.LEFT);
pTextHeader.setAntiAlias(true);
pSquareBg = new Paint();
pSquareFg = new Paint();
pSquareFg.setAntiAlias(true);
pSquareFg.setTextAlign(Align.CENTER);
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
baseLocation.set(0, 0, columnWidth - squareSpacing,
columnWidth - squareSpacing);
baseLocation.offset(getPaddingLeft(), getPaddingTop());
headerOverflow = 0;
previousMonth = "";
previousYear = "";
pTextHeader.setColor(textColor);
updateDate();
GregorianCalendar currentDate = (GregorianCalendar) baseDate.clone();
for (int column = 0; column < nColumns - 1; column++)
{
drawColumn(canvas, baseLocation, currentDate, column);
baseLocation.offset(columnWidth, -columnHeight);
}
drawAxis(canvas, baseLocation);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
@Override
protected void onSizeChanged(int width,
int height,
int oldWidth,
int oldHeight)
{
if (height < 8) height = 200;
float baseSize = height / 8.0f;
setScrollerBucketSize((int) baseSize);
squareSpacing = dpToPixels(getContext(), 1.0f);
float maxTextSize = getDimension(getContext(), R.dimen.regularTextSize);
float textSize = height * 0.06f;
textSize = Math.min(textSize, maxTextSize);
pSquareFg.setTextSize(textSize);
pTextHeader.setTextSize(textSize);
squareTextOffset = pSquareFg.getFontSpacing() * 0.4f;
headerTextOffset = pTextHeader.getFontSpacing() * 0.3f;
float rightLabelWidth = getWeekdayLabelWidth() + headerTextOffset;
float horizontalPadding = getPaddingRight() + getPaddingLeft();
columnWidth = baseSize;
columnHeight = 8 * baseSize;
nColumns =
(int) ((width - rightLabelWidth - horizontalPadding) / baseSize) +
1;
updateDate();
}
private void drawAxis(Canvas canvas, RectF location)
{
float verticalOffset = pTextHeader.getFontSpacing() * 0.4f;
for (String day : DateUtils.getLocaleDayNames(Calendar.SHORT))
{
location.offset(0, columnWidth);
canvas.drawText(day, location.left + headerTextOffset,
location.centerY() + verticalOffset, pTextHeader);
}
}
private void drawColumn(Canvas canvas,
RectF location,
GregorianCalendar date,
int column)
{
drawColumnHeader(canvas, location, date);
location.offset(0, columnWidth);
for (int j = 0; j < 7; j++)
{
if (!(column == nColumns - 2 && getDataOffset() == 0 &&
j > todayPositionInColumn))
{
int checkmarkOffset =
getDataOffset() * 7 + nDays - 7 * (column + 1) +
todayPositionInColumn - j;
drawSquare(canvas, location, date, checkmarkOffset);
}
date.add(Calendar.DAY_OF_MONTH, 1);
location.offset(0, columnWidth);
}
}
private void drawColumnHeader(Canvas canvas,
RectF location,
GregorianCalendar date)
{
String month = dfMonth.format(date.getTime());
String year = dfYear.format(date.getTime());
String text = null;
if (!month.equals(previousMonth)) text = previousMonth = month;
else if (!year.equals(previousYear)) text = previousYear = year;
if (text != null)
{
canvas.drawText(text, location.left + headerOverflow,
location.bottom - headerTextOffset, pTextHeader);
headerOverflow +=
pTextHeader.measureText(text) + columnWidth * 0.2f;
}
headerOverflow = Math.max(0, headerOverflow - columnWidth);
}
private void drawSquare(Canvas canvas,
RectF location,
GregorianCalendar date,
int checkmarkOffset)
{
if (checkmarkOffset >= checkmarks.length) pSquareBg.setColor(colors[0]);
else
{
int checkmark = checkmarks[checkmarkOffset];
if(checkmark == 0) pSquareBg.setColor(colors[0]);
else if(checkmark < target)
{
pSquareBg.setColor(isNumerical ? textColor : colors[1]);
}
else pSquareBg.setColor(colors[2]);
}
pSquareFg.setColor(reverseTextColor);
canvas.drawRect(location, pSquareBg);
String text = Integer.toString(date.get(Calendar.DAY_OF_MONTH));
canvas.drawText(text, location.centerX(),
location.centerY() + squareTextOffset, pSquareFg);
}
private float getWeekdayLabelWidth()
{
float width = 0;
for (String w : DateUtils.getLocaleDayNames(Calendar.SHORT))
width = Math.max(width, pSquareFg.measureText(w));
return width;
}
private void init()
{
isEditable = false;
checkmarks = new int[0];
controller = new Controller() {};
target = 2;
initColors();
initPaints();
initDateFormats();
initRects();
}
private void initColors()
{
StyledResources res = new StyledResources(getContext());
if (isBackgroundTransparent)
primaryColor = ColorUtils.setMinValue(primaryColor, 0.75f);
int red = Color.red(primaryColor);
int green = Color.green(primaryColor);
int blue = Color.blue(primaryColor);
if (isBackgroundTransparent)
{
colors = new int[3];
colors[0] = Color.argb(16, 255, 255, 255);
colors[1] = Color.argb(128, red, green, blue);
colors[2] = primaryColor;
textColor = Color.WHITE;
reverseTextColor = Color.WHITE;
}
else
{
colors = new int[3];
colors[0] = res.getColor(R.attr.lowContrastTextColor);
colors[1] = Color.argb(127, red, green, blue);
colors[2] = primaryColor;
textColor = res.getColor(R.attr.mediumContrastTextColor);
reverseTextColor =
res.getColor(R.attr.highContrastReverseTextColor);
}
}
private void initDateFormats()
{
if (isInEditMode())
{
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
dfYear = new SimpleDateFormat("yyyy", Locale.getDefault());
}
else
{
dfMonth = AndroidDateFormats.fromSkeleton("MMM");
dfYear = AndroidDateFormats.fromSkeleton("yyyy");
}
}
private void initRects()
{
baseLocation = new RectF();
}
@Nullable
private Timestamp positionToTimestamp(float x, float y)
{
int col = (int) (x / columnWidth);
int row = (int) (y / columnWidth);
if (row == 0) return null;
if (col == nColumns - 1) return null;
int offset = col * 7 + (row - 1);
Calendar date = (Calendar) baseDate.clone();
date.add(Calendar.DAY_OF_YEAR, offset);
if (DateUtils.getStartOfDay(date.getTimeInMillis()) >
DateUtils.getStartOfToday()) return null;
return new Timestamp(date.getTimeInMillis());
}
private void updateDate()
{
baseDate = DateUtils.getStartOfTodayCalendar();
baseDate.add(Calendar.DAY_OF_YEAR, -(getDataOffset() - 1) * 7);
nDays = (nColumns - 1) * 7;
int realWeekday =
DateUtils.getStartOfTodayCalendar().get(Calendar.DAY_OF_WEEK);
todayPositionInColumn =
(7 + realWeekday - baseDate.getFirstDayOfWeek()) % 7;
baseDate.add(Calendar.DAY_OF_YEAR, -nDays);
baseDate.add(Calendar.DAY_OF_YEAR, -todayPositionInColumn);
}
public interface Controller
{
default void onToggleCheckmark(Timestamp timestamp) {}
}
}

View File

@@ -1,272 +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.activities.common.views;
import android.content.*;
import android.graphics.*;
import android.support.annotation.*;
import android.text.*;
import android.util.*;
import android.view.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.utils.*;
import static org.isoron.uhabits.utils.AttributeSetUtils.*;
import static org.isoron.androidbase.utils.InterfaceUtils.*;
public class RingView extends View
{
public static final PorterDuffXfermode XFERMODE_CLEAR =
new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
private int color;
private float precision;
private float percentage;
private int diameter;
private float thickness;
private RectF rect;
private TextPaint pRing;
private Integer backgroundColor;
private Integer inactiveColor;
private float em;
private String text;
private float textSize;
private boolean enableFontAwesome;
@Nullable
private Bitmap drawingCache;
private Canvas cacheCanvas;
private boolean isTransparencyEnabled;
public RingView(Context context)
{
super(context);
percentage = 0.0f;
precision = 0.01f;
color = PaletteUtils.getAndroidTestColor(0);
thickness = dpToPixels(getContext(), 2);
text = "";
textSize = getDimension(context, R.dimen.smallTextSize);
init();
}
public RingView(Context ctx, AttributeSet attrs)
{
super(ctx, attrs);
percentage = getFloatAttribute(ctx, attrs, "percentage", 0);
precision = getFloatAttribute(ctx, attrs, "precision", 0.01f);
color = getColorAttribute(ctx, attrs, "color", 0);
backgroundColor = getColorAttribute(ctx, attrs, "backgroundColor", null);
inactiveColor = getColorAttribute(ctx, attrs, "inactiveColor", null);
thickness = getFloatAttribute(ctx, attrs, "thickness", 0);
thickness = dpToPixels(ctx, thickness);
float defaultTextSize = getDimension(ctx, R.dimen.smallTextSize);
textSize = getFloatAttribute(ctx, attrs, "textSize", defaultTextSize);
textSize = spToPixels(ctx, textSize);
text = getAttribute(ctx, attrs, "text", "");
enableFontAwesome =
getBooleanAttribute(ctx, attrs, "enableFontAwesome", false);
init();
}
@Override
public void setBackgroundColor(int backgroundColor)
{
this.backgroundColor = backgroundColor;
invalidate();
}
public void setColor(int color)
{
this.color = color;
invalidate();
}
public int getColor()
{
return color;
}
public void setIsTransparencyEnabled(boolean isTransparencyEnabled)
{
this.isTransparencyEnabled = isTransparencyEnabled;
}
public void setPercentage(float percentage)
{
this.percentage = percentage;
invalidate();
}
public void setPrecision(float precision)
{
this.precision = precision;
invalidate();
}
public void setText(String text)
{
this.text = text;
invalidate();
}
public void setTextSize(float textSize)
{
this.textSize = textSize;
}
public void setThickness(float thickness)
{
this.thickness = thickness;
invalidate();
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Canvas activeCanvas;
if (isTransparencyEnabled)
{
if (drawingCache == null) reallocateCache();
activeCanvas = cacheCanvas;
drawingCache.eraseColor(Color.TRANSPARENT);
}
else
{
activeCanvas = canvas;
}
pRing.setColor(color);
rect.set(0, 0, diameter, diameter);
float angle = 360 * Math.round(percentage / precision) * precision;
activeCanvas.drawArc(rect, -90, angle, true, pRing);
pRing.setColor(inactiveColor);
activeCanvas.drawArc(rect, angle - 90, 360 - angle, true, pRing);
if (thickness > 0)
{
if (isTransparencyEnabled) pRing.setXfermode(XFERMODE_CLEAR);
else pRing.setColor(backgroundColor);
rect.inset(thickness, thickness);
activeCanvas.drawArc(rect, 0, 360, true, pRing);
pRing.setXfermode(null);
pRing.setColor(color);
pRing.setTextSize(textSize);
if (enableFontAwesome)
pRing.setTypeface(getFontAwesome(getContext()));
activeCanvas.drawText(text, rect.centerX(),
rect.centerY() + 0.4f * em, pRing);
}
if (activeCanvas != canvas) canvas.drawBitmap(drawingCache, 0, 0, null);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
diameter = Math.min(height, width);
pRing.setTextSize(textSize);
em = pRing.measureText("M");
setMeasuredDimension(diameter, diameter);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
if (isTransparencyEnabled) reallocateCache();
}
private void init()
{
pRing = new TextPaint();
pRing.setAntiAlias(true);
pRing.setColor(color);
pRing.setTextAlign(Paint.Align.CENTER);
StyledResources res = new StyledResources(getContext());
if (backgroundColor == null)
backgroundColor = res.getColor(R.attr.cardBackgroundColor);
if (inactiveColor == null)
inactiveColor = res.getColor(R.attr.highContrastTextColor);
inactiveColor = ColorUtils.setAlpha(inactiveColor, 0.1f);
rect = new RectF();
}
private void reallocateCache()
{
if (drawingCache != null) drawingCache.recycle();
drawingCache =
Bitmap.createBitmap(diameter, diameter, Bitmap.Config.ARGB_8888);
cacheCanvas = new Canvas(drawingCache);
}
public float getPercentage()
{
return percentage;
}
public float getPrecision()
{
return precision;
}
}

View File

@@ -1,452 +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.activities.common.views;
import android.content.*;
import android.graphics.*;
import android.support.annotation.*;
import android.util.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.utils.*;
import java.text.*;
import java.util.*;
import static org.isoron.androidbase.utils.InterfaceUtils.*;
public class ScoreChart extends ScrollableChart
{
private static final PorterDuffXfermode XFERMODE_CLEAR =
new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
private static final PorterDuffXfermode XFERMODE_SRC =
new PorterDuffXfermode(PorterDuff.Mode.SRC);
private Paint pGrid;
private float em;
private SimpleDateFormat dfMonth;
private SimpleDateFormat dfDay;
private SimpleDateFormat dfYear;
private Paint pText, pGraph;
private RectF rect, prevRect;
private int baseSize;
private int paddingTop;
private float columnWidth;
private int columnHeight;
private int nColumns;
private int textColor;
private int gridColor;
@Nullable
private List<Score> scores;
private int primaryColor;
@Deprecated
private int bucketSize = 7;
private int backgroundColor;
private Bitmap drawingCache;
private Canvas cacheCanvas;
private boolean isTransparencyEnabled;
private int skipYear = 0;
private String previousYearText;
private String previousMonthText;
public ScoreChart(Context context)
{
super(context);
init();
}
public ScoreChart(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
public void populateWithRandomData()
{
Random random = new Random();
scores = new LinkedList<>();
double previous = 0.5f;
Timestamp timestamp = DateUtils.getToday();
for (int i = 1; i < 100; i++)
{
double step = 0.1f;
double current = previous + random.nextDouble() * step * 2 - step;
current = Math.max(0, Math.min(1.0f, current));
scores.add(new Score(timestamp.minus(i), current));
previous = current;
}
}
@Deprecated
public void setBucketSize(int bucketSize)
{
this.bucketSize = bucketSize;
postInvalidate();
}
public void setIsTransparencyEnabled(boolean enabled)
{
this.isTransparencyEnabled = enabled;
postInvalidate();
}
public void setColor(int primaryColor)
{
this.primaryColor = primaryColor;
postInvalidate();
}
public void setScores(@NonNull List<Score> scores)
{
this.scores = scores;
postInvalidate();
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Canvas activeCanvas;
if (isTransparencyEnabled)
{
if (drawingCache == null) initCache(getWidth(), getHeight());
activeCanvas = cacheCanvas;
drawingCache.eraseColor(Color.TRANSPARENT);
}
else
{
activeCanvas = canvas;
}
if (scores == null) return;
rect.set(0, 0, nColumns * columnWidth, columnHeight);
rect.offset(0, paddingTop);
drawGrid(activeCanvas, rect);
pText.setColor(textColor);
pGraph.setColor(primaryColor);
prevRect.setEmpty();
previousMonthText = "";
previousYearText = "";
skipYear = 0;
for (int k = 0; k < nColumns; k++)
{
int offset = nColumns - k - 1 + getDataOffset();
if (offset >= scores.size()) continue;
double score = scores.get(offset).getValue();
Timestamp timestamp = scores.get(offset).getTimestamp();
int height = (int) (columnHeight * score);
rect.set(0, 0, baseSize, baseSize);
rect.offset(k * columnWidth + (columnWidth - baseSize) / 2,
paddingTop + columnHeight - height - baseSize / 2);
if (!prevRect.isEmpty())
{
drawLine(activeCanvas, prevRect, rect);
drawMarker(activeCanvas, prevRect);
}
if (k == nColumns - 1) drawMarker(activeCanvas, rect);
prevRect.set(rect);
rect.set(0, 0, columnWidth, columnHeight);
rect.offset(k * columnWidth, paddingTop);
drawFooter(activeCanvas, rect, timestamp);
}
if (activeCanvas != canvas) canvas.drawBitmap(drawingCache, 0, 0, null);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
@Override
protected void onSizeChanged(int width,
int height,
int oldWidth,
int oldHeight)
{
if (height < 9) height = 200;
float maxTextSize = getDimension(getContext(), R.dimen.tinyTextSize);
float textSize = height * 0.06f;
pText.setTextSize(Math.min(textSize, maxTextSize));
em = pText.getFontSpacing();
int footerHeight = (int) (3 * em);
paddingTop = (int) (em);
baseSize = (height - footerHeight - paddingTop) / 8;
columnWidth = baseSize;
columnWidth = Math.max(columnWidth, getMaxDayWidth() * 1.5f);
columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f);
nColumns = (int) (width / columnWidth);
columnWidth = (float) width / nColumns;
setScrollerBucketSize((int) columnWidth);
columnHeight = 8 * baseSize;
float minStrokeWidth = dpToPixels(getContext(), 1);
pGraph.setTextSize(baseSize * 0.5f);
pGraph.setStrokeWidth(baseSize * 0.1f);
pGrid.setStrokeWidth(Math.min(minStrokeWidth, baseSize * 0.05f));
if (isTransparencyEnabled) initCache(width, height);
}
private void drawFooter(Canvas canvas, RectF rect, Timestamp currentDate)
{
String yearText = dfYear.format(currentDate.toJavaDate());
String monthText = dfMonth.format(currentDate.toJavaDate());
String dayText = dfDay.format(currentDate.toJavaDate());
GregorianCalendar calendar = currentDate.toCalendar();
String text;
int year = calendar.get(Calendar.YEAR);
boolean shouldPrintYear = true;
if (yearText.equals(previousYearText)) shouldPrintYear = false;
if (bucketSize >= 365 && (year % 2) != 0) shouldPrintYear = false;
if (skipYear > 0)
{
skipYear--;
shouldPrintYear = false;
}
if (shouldPrintYear)
{
previousYearText = yearText;
previousMonthText = "";
pText.setTextAlign(Paint.Align.CENTER);
canvas.drawText(yearText, rect.centerX(), rect.bottom + em * 2.2f,
pText);
skipYear = 1;
}
if (bucketSize < 365)
{
if (!monthText.equals(previousMonthText))
{
previousMonthText = monthText;
text = monthText;
}
else
{
text = dayText;
}
pText.setTextAlign(Paint.Align.CENTER);
canvas.drawText(text, rect.centerX(), rect.bottom + em * 1.2f,
pText);
}
}
private void drawGrid(Canvas canvas, RectF rGrid)
{
int nRows = 5;
float rowHeight = rGrid.height() / nRows;
pText.setTextAlign(Paint.Align.LEFT);
pText.setColor(textColor);
pGrid.setColor(gridColor);
for (int i = 0; i < nRows; i++)
{
canvas.drawText(String.format("%d%%", (100 - i * 100 / nRows)),
rGrid.left + 0.5f * em, rGrid.top + 1f * em, pText);
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top,
pGrid);
rGrid.offset(0, rowHeight);
}
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid);
}
private void drawLine(Canvas canvas, RectF rectFrom, RectF rectTo)
{
pGraph.setColor(primaryColor);
canvas.drawLine(rectFrom.centerX(), rectFrom.centerY(),
rectTo.centerX(), rectTo.centerY(), pGraph);
}
private void drawMarker(Canvas canvas, RectF rect)
{
rect.inset(baseSize * 0.225f, baseSize * 0.225f);
setModeOrColor(pGraph, XFERMODE_CLEAR, backgroundColor);
canvas.drawOval(rect, pGraph);
rect.inset(baseSize * 0.1f, baseSize * 0.1f);
setModeOrColor(pGraph, XFERMODE_SRC, primaryColor);
canvas.drawOval(rect, pGraph);
// rect.inset(baseSize * 0.1f, baseSize * 0.1f);
// setModeOrColor(pGraph, XFERMODE_CLEAR, backgroundColor);
// canvas.drawOval(rect, pGraph);
if (isTransparencyEnabled) pGraph.setXfermode(XFERMODE_SRC);
}
private float getMaxDayWidth()
{
float maxDayWidth = 0;
GregorianCalendar day = DateUtils.getStartOfTodayCalendar();
for (int i = 0; i < 28; i++)
{
day.set(Calendar.DAY_OF_MONTH, i);
float monthWidth = pText.measureText(dfMonth.format(day.getTime()));
maxDayWidth = Math.max(maxDayWidth, monthWidth);
}
return maxDayWidth;
}
private float getMaxMonthWidth()
{
float maxMonthWidth = 0;
GregorianCalendar day = DateUtils.getStartOfTodayCalendar();
for (int i = 0; i < 12; i++)
{
day.set(Calendar.MONTH, i);
float monthWidth = pText.measureText(dfMonth.format(day.getTime()));
maxMonthWidth = Math.max(maxMonthWidth, monthWidth);
}
return maxMonthWidth;
}
private void init()
{
initPaints();
initColors();
initDateFormats();
initRects();
}
private void initCache(int width, int height)
{
if (drawingCache != null) drawingCache.recycle();
drawingCache =
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
cacheCanvas = new Canvas(drawingCache);
}
private void initColors()
{
StyledResources res = new StyledResources(getContext());
primaryColor = Color.BLACK;
textColor = res.getColor(R.attr.mediumContrastTextColor);
gridColor = res.getColor(R.attr.lowContrastTextColor);
backgroundColor = res.getColor(R.attr.cardBackgroundColor);
}
private void initDateFormats()
{
if (isInEditMode())
{
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
dfYear = new SimpleDateFormat("yyyy", Locale.getDefault());
dfDay = new SimpleDateFormat("d", Locale.getDefault());
}
else
{
dfMonth = AndroidDateFormats.fromSkeleton("MMM");
dfYear = AndroidDateFormats.fromSkeleton("yyyy");
dfDay = AndroidDateFormats.fromSkeleton("d");
}
}
private void initPaints()
{
pText = new Paint();
pText.setAntiAlias(true);
pGraph = new Paint();
pGraph.setTextAlign(Paint.Align.CENTER);
pGraph.setAntiAlias(true);
pGrid = new Paint();
pGrid.setAntiAlias(true);
}
private void initRects()
{
rect = new RectF();
prevRect = new RectF();
}
private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color)
{
if (isTransparencyEnabled) p.setXfermode(mode);
else p.setColor(color);
}
}

View File

@@ -1,238 +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.activities.common.views;
import android.animation.*;
import android.content.*;
import android.os.*;
import android.util.*;
import android.view.*;
import android.widget.*;
public abstract class ScrollableChart extends View
implements GestureDetector.OnGestureListener,
ValueAnimator.AnimatorUpdateListener
{
private int dataOffset;
private int scrollerBucketSize = 1;
private int direction = 1;
private GestureDetector detector;
private Scroller scroller;
private ValueAnimator scrollAnimator;
private ScrollController scrollController;
private int maxDataOffset = 10000;
public ScrollableChart(Context context)
{
super(context);
init(context);
}
public ScrollableChart(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context);
}
public int getDataOffset()
{
return dataOffset;
}
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
if (!scroller.isFinished())
{
scroller.computeScrollOffset();
updateDataOffset();
}
else
{
scrollAnimator.cancel();
}
}
@Override
public boolean onDown(MotionEvent e)
{
return true;
}
@Override
public boolean onFling(MotionEvent e1,
MotionEvent e2,
float velocityX,
float velocityY)
{
scroller.fling(scroller.getCurrX(), scroller.getCurrY(),
direction * ((int) velocityX) / 2, 0, 0, getMaxX(), 0, 0);
invalidate();
scrollAnimator.setDuration(scroller.getDuration());
scrollAnimator.start();
return false;
}
private int getMaxX()
{
return maxDataOffset * scrollerBucketSize;
}
@Override
public void onRestoreInstanceState(Parcelable state)
{
if(!(state instanceof BundleSavedState))
{
super.onRestoreInstanceState(state);
return;
}
BundleSavedState bss = (BundleSavedState) state;
int x = bss.bundle.getInt("x");
int y = bss.bundle.getInt("y");
direction = bss.bundle.getInt("direction");
dataOffset = bss.bundle.getInt("dataOffset");
maxDataOffset = bss.bundle.getInt("maxDataOffset");
scroller.startScroll(0, 0, x, y, 0);
scroller.computeScrollOffset();
super.onRestoreInstanceState(bss.getSuperState());
}
@Override
public Parcelable onSaveInstanceState()
{
Parcelable superState = super.onSaveInstanceState();
Bundle bundle = new Bundle();
bundle.putInt("x", scroller.getCurrX());
bundle.putInt("y", scroller.getCurrY());
bundle.putInt("dataOffset", dataOffset);
bundle.putInt("direction", direction);
bundle.putInt("maxDataOffset", maxDataOffset);
return new BundleSavedState(superState, bundle);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy)
{
if (scrollerBucketSize == 0) return false;
if (Math.abs(dx) > Math.abs(dy))
{
ViewParent parent = getParent();
if (parent != null) parent.requestDisallowInterceptTouchEvent(true);
}
dx = - direction * dx;
dx = Math.min(dx, getMaxX() - scroller.getCurrX());
scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) dx,
(int) dy, 0);
scroller.computeScrollOffset();
updateDataOffset();
return true;
}
@Override
public void onShowPress(MotionEvent e)
{
}
@Override
public boolean onSingleTapUp(MotionEvent e)
{
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
return detector.onTouchEvent(event);
}
public void setScrollDirection(int direction)
{
if (direction != 1 && direction != -1)
throw new IllegalArgumentException();
this.direction = direction;
}
@Override
public void onLongPress(MotionEvent e)
{
}
public void setMaxDataOffset(int maxDataOffset)
{
this.maxDataOffset = maxDataOffset;
this.dataOffset = Math.min(dataOffset, maxDataOffset);
scrollController.onDataOffsetChanged(this.dataOffset);
postInvalidate();
}
public void setScrollController(ScrollController scrollController)
{
this.scrollController = scrollController;
}
public void setScrollerBucketSize(int scrollerBucketSize)
{
this.scrollerBucketSize = scrollerBucketSize;
}
private void init(Context context)
{
detector = new GestureDetector(context, this);
scroller = new Scroller(context, null, true);
scrollAnimator = ValueAnimator.ofFloat(0, 1);
scrollAnimator.addUpdateListener(this);
scrollController = new ScrollController() {};
}
private void updateDataOffset()
{
int newDataOffset = scroller.getCurrX() / scrollerBucketSize;
newDataOffset = Math.max(0, newDataOffset);
newDataOffset = Math.min(maxDataOffset, newDataOffset);
if (newDataOffset != dataOffset)
{
dataOffset = newDataOffset;
scrollController.onDataOffsetChanged(dataOffset);
postInvalidate();
}
}
public interface ScrollController
{
default void onDataOffsetChanged(int newDataOffset) {}
}
}

View File

@@ -1,298 +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.activities.common.views;
import android.content.*;
import android.graphics.*;
import android.util.*;
import android.view.*;
import android.view.ViewGroup.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.utils.*;
import java.text.*;
import java.util.*;
import static android.view.View.MeasureSpec.*;
import static org.isoron.androidbase.utils.InterfaceUtils.getDimension;
public class StreakChart extends View
{
private Paint paint;
private long minLength;
private long maxLength;
private int[] colors;
private RectF rect;
private int baseSize;
private int primaryColor;
private List<Streak> streaks;
private boolean isBackgroundTransparent;
private DateFormat dateFormat;
private int width;
private float em;
private float maxLabelWidth;
private float textMargin;
private boolean shouldShowLabels;
private int textColor;
private int reverseTextColor;
public StreakChart(Context context)
{
super(context);
init();
}
public StreakChart(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
/**
* Returns the maximum number of streaks this view is able to show, given
* its current size.
*
* @return max number of visible streaks
*/
public int getMaxStreakCount()
{
return (int) Math.floor(getMeasuredHeight() / baseSize);
}
public void populateWithRandomData()
{
Timestamp start = DateUtils.getToday();
LinkedList<Streak> streaks = new LinkedList<>();
for (int i = 0; i < 10; i++)
{
int length = new Random().nextInt(100);
Timestamp end = start.plus(length);
streaks.add(new Streak(start, end));
start = end.plus(1);
}
setStreaks(streaks);
}
public void setColor(int color)
{
this.primaryColor = color;
postInvalidate();
}
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
{
this.isBackgroundTransparent = isBackgroundTransparent;
initColors();
}
public void setStreaks(List<Streak> streaks)
{
this.streaks = streaks;
initColors();
updateMaxMinLengths();
requestLayout();
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if (streaks.size() == 0) return;
rect.set(0, 0, width, baseSize);
for (Streak s : streaks)
{
drawRow(canvas, s, rect);
rect.offset(0, baseSize);
}
}
@Override
protected void onMeasure(int widthSpec, int heightSpec)
{
LayoutParams params = getLayoutParams();
if (params != null && params.height == LayoutParams.WRAP_CONTENT)
{
int width = getSize(widthSpec);
int height = streaks.size() * baseSize;
heightSpec = makeMeasureSpec(height, EXACTLY);
widthSpec = makeMeasureSpec(width, EXACTLY);
}
setMeasuredDimension(widthSpec, heightSpec);
}
@Override
protected void onSizeChanged(int width,
int height,
int oldWidth,
int oldHeight)
{
this.width = width;
Context context = getContext();
float minTextSize = getDimension(context, R.dimen.tinyTextSize);
float maxTextSize = getDimension(context, R.dimen.regularTextSize);
float textSize = baseSize * 0.5f;
paint.setTextSize(
Math.max(Math.min(textSize, maxTextSize), minTextSize));
em = paint.getFontSpacing();
textMargin = 0.5f * em;
updateMaxMinLengths();
}
private void drawRow(Canvas canvas, Streak streak, RectF rect)
{
if (maxLength == 0) return;
float percentage = (float) streak.getLength() / maxLength;
float availableWidth = width - 2 * maxLabelWidth;
if (shouldShowLabels) availableWidth -= 2 * textMargin;
float barWidth = percentage * availableWidth;
float minBarWidth =
paint.measureText(Long.toString(streak.getLength())) + em;
barWidth = Math.max(barWidth, minBarWidth);
float gap = (width - barWidth) / 2;
float paddingTopBottom = baseSize * 0.05f;
paint.setColor(percentageToColor(percentage));
canvas.drawRect(rect.left + gap, rect.top + paddingTopBottom,
rect.right - gap, rect.bottom - paddingTopBottom, paint);
float yOffset = rect.centerY() + 0.3f * em;
paint.setColor(reverseTextColor);
paint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(Long.toString(streak.getLength()), rect.centerX(),
yOffset, paint);
if (shouldShowLabels)
{
String startLabel = dateFormat.format(streak.getStart().toJavaDate());
String endLabel = dateFormat.format(streak.getEnd().toJavaDate());
paint.setColor(textColor);
paint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText(startLabel, gap - textMargin, yOffset, paint);
paint.setTextAlign(Paint.Align.LEFT);
canvas.drawText(endLabel, width - gap + textMargin, yOffset, paint);
}
}
private void init()
{
initPaints();
initColors();
streaks = Collections.emptyList();
dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM);
if (!isInEditMode()) dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
rect = new RectF();
baseSize = getResources().getDimensionPixelSize(R.dimen.baseSize);
}
private void initColors()
{
int red = Color.red(primaryColor);
int green = Color.green(primaryColor);
int blue = Color.blue(primaryColor);
StyledResources res = new StyledResources(getContext());
colors = new int[4];
colors[3] = primaryColor;
colors[2] = Color.argb(192, red, green, blue);
colors[1] = Color.argb(96, red, green, blue);
colors[0] = res.getColor(R.attr.lowContrastTextColor);
textColor = res.getColor(R.attr.mediumContrastTextColor);
reverseTextColor = res.getColor(R.attr.highContrastReverseTextColor);
}
private void initPaints()
{
paint = new Paint();
paint.setTextAlign(Paint.Align.CENTER);
paint.setAntiAlias(true);
}
private int percentageToColor(float percentage)
{
if (percentage >= 1.0f) return colors[3];
if (percentage >= 0.8f) return colors[2];
if (percentage >= 0.5f) return colors[1];
return colors[0];
}
private void updateMaxMinLengths()
{
maxLength = 0;
minLength = Long.MAX_VALUE;
shouldShowLabels = true;
for (Streak s : streaks)
{
maxLength = Math.max(maxLength, s.getLength());
minLength = Math.min(minLength, s.getLength());
float lw1 =
paint.measureText(dateFormat.format(s.getStart().toJavaDate()));
float lw2 =
paint.measureText(dateFormat.format(s.getEnd().toJavaDate()));
maxLabelWidth = Math.max(maxLabelWidth, Math.max(lw1, lw2));
}
if (width - 2 * maxLabelWidth < width * 0.25f)
{
maxLabelWidth = 0;
shouldShowLabels = false;
}
}
}

View File

@@ -1,66 +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.activities.common.views
import android.content.*
import android.widget.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.core.tasks.*
class TaskProgressBar(
context: Context,
private val runner: TaskRunner
) : ProgressBar(
context,
null,
android.R.attr.progressBarStyleHorizontal
), TaskRunner.Listener {
init {
visibility = BaseRootView.GONE
isIndeterminate = true
}
override fun onTaskStarted(task: Task?) = update()
override fun onTaskFinished(task: Task?) = update()
override fun onAttachedToWindow() {
super.onAttachedToWindow()
runner.addListener(this)
update()
}
override fun onDetachedFromWindow() {
runner.removeListener(this)
super.onDetachedFromWindow()
}
fun update() {
val callback = {
val activeTaskCount = runner.activeTaskCount
val newVisibility = when (activeTaskCount) {
0 -> GONE
else -> VISIBLE
}
if (visibility != newVisibility) visibility = newVisibility
}
postDelayed(callback, 500)
}
}

View File

@@ -1,281 +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.activities.habits.edit;
import android.app.Dialog;
import android.content.*;
import android.os.*;
import android.support.annotation.*;
import android.support.v7.app.*;
import android.text.format.*;
import android.view.*;
import com.android.datetimepicker.time.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.*;
import org.isoron.uhabits.activities.common.dialogs.*;
import org.isoron.uhabits.activities.habits.edit.views.*;
import org.isoron.uhabits.core.commands.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.preferences.*;
import butterknife.*;
import static android.view.View.GONE;
import static org.isoron.uhabits.core.ui.ThemeSwitcher.THEME_LIGHT;
public class EditHabitDialog extends AppCompatDialogFragment
{
public static final String BUNDLE_HABIT_ID = "habitId";
public static final String BUNDLE_HABIT_TYPE = "habitType";
protected Habit originalHabit;
protected Preferences prefs;
protected CommandRunner commandRunner;
protected HabitList habitList;
protected HabitsApplicationComponent component;
protected ModelFactory modelFactory;
@BindView(R.id.namePanel)
NameDescriptionPanel namePanel;
@BindView(R.id.reminderPanel)
ReminderPanel reminderPanel;
@BindView(R.id.frequencyPanel)
FrequencyPanel frequencyPanel;
@BindView(R.id.targetPanel)
TargetPanel targetPanel;
private ColorPickerDialogFactory colorPickerDialogFactory;
@Override
public int getTheme()
{
HabitsApplicationComponent component =
((HabitsApplication) getContext().getApplicationContext()).getComponent();
if(component.getPreferences().getTheme() == THEME_LIGHT)
return R.style.DialogWithTitle;
else
return R.style.DarkDialogWithTitle;
}
@Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
HabitsActivity activity = (HabitsActivity) getActivity();
colorPickerDialogFactory =
activity.getComponent().getColorPickerDialogFactory();
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState)
{
View view;
view = inflater.inflate(R.layout.edit_habit, container, false);
initDependencies();
ButterKnife.bind(this, view);
originalHabit = parseHabitFromArguments();
getDialog().setTitle(getTitle());
populateForm();
setupReminderController();
setupNameController();
return view;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Dialog dialog = super.onCreateDialog(savedInstanceState);
final Window window = dialog.getWindow();
if (window != null) {
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
}
return dialog;
}
protected int getTitle()
{
if (originalHabit != null) return R.string.edit_habit;
else return R.string.create_habit;
}
protected void saveHabit(@NonNull Habit habit)
{
if (originalHabit == null)
{
commandRunner.execute(component
.getCreateHabitCommandFactory()
.create(habitList, habit), null);
}
else
{
commandRunner.execute(component.getEditHabitCommandFactory().
create(habitList, originalHabit, habit), originalHabit.getId());
}
}
private int getTypeFromArguments()
{
return getArguments().getInt(BUNDLE_HABIT_TYPE);
}
private void initDependencies()
{
Context appContext = getContext().getApplicationContext();
HabitsApplication app = (HabitsApplication) appContext;
component = app.getComponent();
prefs = component.getPreferences();
habitList = component.getHabitList();
commandRunner = component.getCommandRunner();
modelFactory = component.getModelFactory();
}
@OnClick(R.id.buttonDiscard)
void onButtonDiscardClick()
{
dismiss();
}
@OnClick(R.id.buttonSave)
void onSaveButtonClick()
{
int type = getTypeFromArguments();
if (!namePanel.validate()) return;
if (type == Habit.YES_NO_HABIT && !frequencyPanel.validate()) return;
if (type == Habit.NUMBER_HABIT && !targetPanel.validate()) return;
Habit habit = modelFactory.buildHabit();
if( originalHabit != null )
habit.copyFrom(originalHabit);
habit.setName(namePanel.getName());
habit.setDescription(namePanel.getDescription());
habit.setColor(namePanel.getColor());
habit.setReminder(reminderPanel.getReminder());
habit.setFrequency(frequencyPanel.getFrequency());
habit.setUnit(targetPanel.getUnit());
habit.setTargetValue(targetPanel.getTargetValue());
habit.setType(type);
saveHabit(habit);
dismiss();
}
@Nullable
private Habit parseHabitFromArguments()
{
Bundle arguments = getArguments();
if (arguments == null) return null;
Long id = (Long) arguments.get(BUNDLE_HABIT_ID);
if (id == null) return null;
Habit habit = habitList.getById(id);
if (habit == null) throw new IllegalStateException();
return habit;
}
private void populateForm()
{
Habit habit = modelFactory.buildHabit();
habit.setFrequency(Frequency.DAILY);
habit.setColor(prefs.getDefaultHabitColor(habit.getColor()));
habit.setType(getTypeFromArguments());
if (originalHabit != null) habit.copyFrom(originalHabit);
if (habit.isNumerical()) frequencyPanel.setVisibility(GONE);
else targetPanel.setVisibility(GONE);
namePanel.populateFrom(habit);
frequencyPanel.setFrequency(habit.getFrequency());
targetPanel.setTargetValue(habit.getTargetValue());
targetPanel.setUnit(habit.getUnit());
if (habit.hasReminder()) reminderPanel.setReminder(habit.getReminder());
}
private void setupNameController()
{
namePanel.setController(new NameDescriptionPanel.Controller()
{
@Override
public void onColorPickerClicked(int previousColor)
{
ColorPickerDialog picker =
colorPickerDialogFactory.create(previousColor);
picker.setListener(c ->
{
prefs.setDefaultHabitColor(c);
namePanel.setColor(c);
});
picker.show(getFragmentManager(), "picker");
}
});
}
private void setupReminderController()
{
reminderPanel.setController(new ReminderPanel.Controller()
{
@Override
public void onTimeClicked(int currentHour, int currentMin)
{
TimePickerDialog timePicker;
boolean is24HourMode = DateFormat.is24HourFormat(getContext());
timePicker =
TimePickerDialog.newInstance(reminderPanel, currentHour,
currentMin, is24HourMode);
timePicker.show(getFragmentManager(), "timePicker");
}
@Override
public void onWeekdayClicked(WeekdayList currentDays)
{
WeekdayPickerDialog dialog = new WeekdayPickerDialog();
dialog.setListener(reminderPanel);
dialog.setSelectedDays(currentDays);
dialog.show(getFragmentManager(), "weekdayPicker");
}
});
}
}

View File

@@ -1,68 +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.activities.habits.edit;
import android.os.*;
import android.support.annotation.*;
import org.isoron.uhabits.core.models.*;
import javax.inject.*;
import static org.isoron.uhabits.activities.habits.edit.EditHabitDialog.*;
public class EditHabitDialogFactory
{
@Inject
public EditHabitDialogFactory()
{
}
public EditHabitDialog createBoolean()
{
EditHabitDialog dialog = new EditHabitDialog();
Bundle args = new Bundle();
args.putInt(BUNDLE_HABIT_TYPE, Habit.YES_NO_HABIT);
dialog.setArguments(args);
return dialog;
}
public EditHabitDialog createNumerical()
{
EditHabitDialog dialog = new EditHabitDialog();
Bundle args = new Bundle();
args.putInt(BUNDLE_HABIT_TYPE, Habit.NUMBER_HABIT);
dialog.setArguments(args);
return dialog;
}
public EditHabitDialog edit(@NonNull Habit habit)
{
if (habit.getId() == null)
throw new IllegalArgumentException("habit not saved");
EditHabitDialog dialog = new EditHabitDialog();
Bundle args = new Bundle();
args.putLong(BUNDLE_HABIT_ID, habit.getId());
args.putInt(BUNDLE_HABIT_TYPE, habit.getType());
dialog.setArguments(args);
return dialog;
}
}

View File

@@ -1,114 +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.activities.habits.edit.views;
import android.content.*;
import android.support.annotation.*;
import android.text.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*;
import static org.isoron.uhabits.utils.AttributeSetUtils.*;
/**
* An EditText that shows an example usage when there is no text
* currently set. The example disappears when the widget gains focus.
*/
public class ExampleEditText extends EditText
implements View.OnFocusChangeListener
{
private String example;
private String realText;
private int color;
private int exampleColor;
private int inputType;
public ExampleEditText(Context context, @Nullable AttributeSet attrs)
{
super(context, attrs);
if (attrs != null)
example = getAttribute(context, attrs, "example", "");
inputType = getInputType();
realText = getText().toString();
color = getCurrentTextColor();
init();
}
public String getRealText()
{
if(hasFocus()) return getText().toString();
else return realText;
}
@Override
public void onFocusChange(View v, boolean hasFocus)
{
if (!hasFocus) realText = getText().toString();
updateText();
}
public void setExample(String example)
{
this.example = example;
updateText();
}
public void setRealText(String realText)
{
this.realText = realText;
updateText();
}
private void init()
{
StyledResources sr = new StyledResources(getContext());
exampleColor = sr.getColor(R.attr.mediumContrastTextColor);
setOnFocusChangeListener(this);
updateText();
}
private void updateText()
{
if (realText.isEmpty() && !isFocused())
{
setTextColor(exampleColor);
setText(example);
setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
}
else
{
setText(realText);
setTextColor(color);
setInputType(inputType);
}
}
}

View File

@@ -1,164 +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.activities.habits.edit.views;
import android.annotation.*;
import android.content.*;
import android.content.res.*;
import android.support.annotation.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.core.models.*;
import butterknife.*;
import static org.isoron.uhabits.R.id.*;
public class FrequencyPanel extends FrameLayout
{
@BindView(numerator)
TextView tvNumerator;
@BindView(R.id.denominator)
TextView tvDenominator;
@BindView(R.id.spinner)
Spinner spinner;
@BindView(R.id.customFreqPanel)
ViewGroup customFreqPanel;
public FrequencyPanel(@NonNull Context context,
@Nullable AttributeSet attrs)
{
super(context, attrs);
View view = inflate(context, R.layout.edit_habit_frequency, null);
ButterKnife.bind(this, view);
addView(view);
}
@NonNull
public Frequency getFrequency()
{
String freqNum = tvNumerator.getText().toString();
String freqDen = tvDenominator.getText().toString();
if (!freqNum.isEmpty() && !freqDen.isEmpty())
{
int numerator = Integer.parseInt(freqNum);
int denominator = Integer.parseInt(freqDen);
return new Frequency(numerator, denominator);
}
return Frequency.DAILY;
}
@SuppressLint("SetTextI18n")
public void setFrequency(@NonNull Frequency freq)
{
int position = getQuickSelectPosition(freq);
if (position >= 0) showSimplifiedFrequency(position);
else showCustomFrequency();
tvNumerator.setText(Integer.toString(freq.getNumerator()));
tvDenominator.setText(Integer.toString(freq.getDenominator()));
}
@OnItemSelected(R.id.spinner)
public void onFrequencySelected(int position)
{
if (position < 0 || position > 4) throw new IllegalArgumentException();
int freqNums[] = { 1, 1, 2, 5, 3 };
int freqDens[] = { 1, 7, 7, 7, 7 };
setFrequency(new Frequency(freqNums[position], freqDens[position]));
}
public boolean validate()
{
boolean valid = true;
Resources res = getResources();
String freqNum = tvNumerator.getText().toString();
String freqDen = tvDenominator.getText().toString();
if (freqDen.isEmpty())
{
tvDenominator.setError(
res.getString(R.string.validation_show_not_be_blank));
valid = false;
}
if (freqNum.isEmpty())
{
tvNumerator.setError(
res.getString(R.string.validation_show_not_be_blank));
valid = false;
}
if (!valid) return false;
int numerator = Integer.parseInt(freqNum);
int denominator = Integer.parseInt(freqDen);
if (numerator <= 0)
{
tvNumerator.setError(
res.getString(R.string.validation_number_should_be_positive));
valid = false;
}
if (numerator > denominator)
{
tvNumerator.setError(
res.getString(R.string.validation_at_most_one_rep_per_day));
valid = false;
}
return valid;
}
private int getQuickSelectPosition(@NonNull Frequency freq)
{
if (freq.equals(Frequency.DAILY)) return 0;
if (freq.equals(Frequency.WEEKLY)) return 1;
if (freq.equals(Frequency.TWO_TIMES_PER_WEEK)) return 2;
if (freq.equals(Frequency.FIVE_TIMES_PER_WEEK)) return 3;
return -1;
}
private void showCustomFrequency()
{
spinner.setVisibility(View.GONE);
customFreqPanel.setVisibility(View.VISIBLE);
}
private void showSimplifiedFrequency(int quickSelectPosition)
{
spinner.setVisibility(View.VISIBLE);
spinner.setSelection(quickSelectPosition);
customFreqPanel.setVisibility(View.GONE);
}
}

View File

@@ -1,152 +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.activities.habits.edit.views;
import android.content.*;
import android.content.res.*;
import android.os.*;
import android.support.annotation.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.utils.*;
import butterknife.*;
public class NameDescriptionPanel extends FrameLayout
{
@BindView(R.id.tvName)
EditText tvName;
@BindView(R.id.tvDescription)
ExampleEditText tvDescription;
private int color;
@NonNull
private Controller controller;
public NameDescriptionPanel(@NonNull Context context,
@Nullable AttributeSet attrs)
{
super(context, attrs);
View view = inflate(context, R.layout.edit_habit_name, null);
ButterKnife.bind(this, view);
addView(view);
controller = new Controller() {};
}
public int getColor()
{
return color;
}
public void setColor(int color)
{
this.color = color;
tvName.setTextColor(PaletteUtils.getColor(getContext(), color));
}
@NonNull
public String getDescription()
{
return tvDescription.getRealText().trim();
}
@NonNull
public String getName()
{
return tvName.getText().toString().trim();
}
public void populateFrom(@NonNull Habit habit)
{
Resources res = getResources();
if(habit.isNumerical())
tvDescription.setExample(res.getString(R.string.example_question_numerical));
else
tvDescription.setExample(res.getString(R.string.example_question_boolean));
setColor(habit.getColor());
tvName.setText(habit.getName());
tvDescription.setRealText(habit.getDescription());
}
public boolean validate()
{
Resources res = getResources();
if (getName().isEmpty())
{
tvName.setError(
res.getString(R.string.validation_name_should_not_be_blank));
return false;
}
return true;
}
@Override
protected void onRestoreInstanceState(Parcelable state)
{
BundleSavedState bss = (BundleSavedState) state;
setColor(bss.bundle.getInt("color"));
super.onRestoreInstanceState(bss.getSuperState());
}
@Override
protected Parcelable onSaveInstanceState()
{
Parcelable superState = super.onSaveInstanceState();
Bundle bundle = new Bundle();
bundle.putInt("color", color);
return new BundleSavedState(superState, bundle);
}
@OnClick(R.id.buttonPickColor)
void showColorPicker()
{
controller.onColorPickerClicked(color);
}
public void setController(@NonNull Controller controller)
{
this.controller = controller;
}
public interface Controller
{
/**
* Called when the user has clicked the widget to select a new
* color for the habit.
*
* @param previousColor the color previously selected
*/
default void onColorPickerClicked(int previousColor) {}
}
}

View File

@@ -1,195 +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.activities.habits.edit.views;
import android.content.*;
import android.os.*;
import android.support.annotation.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import com.android.datetimepicker.time.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.common.dialogs.*;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.utils.*;
import butterknife.*;
public class ReminderPanel extends FrameLayout
implements TimePickerDialog.OnTimeSetListener,
WeekdayPickerDialog.OnWeekdaysPickedListener
{
@BindView(R.id.tvReminderTime)
TextView tvReminderTime;
@BindView(R.id.llReminderDays)
ViewGroup llReminderDays;
@BindView(R.id.tvReminderDays)
TextView tvReminderDays;
@Nullable
private Reminder reminder;
@NonNull
private Controller controller;
public ReminderPanel(@NonNull Context context, @Nullable AttributeSet attrs)
{
super(context, attrs);
View view = inflate(context, R.layout.edit_habit_reminder, null);
ButterKnife.bind(this, view);
addView(view);
controller = new Controller() {};
setReminder(null);
}
@Nullable
public Reminder getReminder()
{
return reminder;
}
public void setReminder(@Nullable Reminder reminder)
{
this.reminder = reminder;
if (reminder == null)
{
tvReminderTime.setText(R.string.reminder_off);
llReminderDays.setVisibility(View.GONE);
return;
}
Context ctx = getContext();
String time = AndroidDateUtils.formatTime(ctx, reminder.getHour(), reminder.getMinute());
tvReminderTime.setText(time);
llReminderDays.setVisibility(View.VISIBLE);
boolean weekdays[] = reminder.getDays().toArray();
tvReminderDays.setText(AndroidDateUtils.formatWeekdayList(ctx, weekdays));
}
@Override
public void onTimeCleared(RadialPickerLayout view)
{
setReminder(null);
}
@Override
public void onTimeSet(RadialPickerLayout view, int hour, int minute)
{
WeekdayList days = WeekdayList.EVERY_DAY;
if (reminder != null) days = reminder.getDays();
setReminder(new Reminder(hour, minute, days));
}
@Override
public void onWeekdaysSet(WeekdayList selectedDays)
{
if (reminder == null) return;
if (selectedDays.isEmpty()) selectedDays = WeekdayList.EVERY_DAY;
setReminder(new Reminder(reminder.getHour(), reminder.getMinute(),
selectedDays));
}
public void setController(@NonNull Controller controller)
{
this.controller = controller;
}
@Override
protected void onRestoreInstanceState(Parcelable state)
{
BundleSavedState bss = (BundleSavedState) state;
if (!bss.bundle.isEmpty())
{
int days = bss.bundle.getInt("days");
int hour = bss.bundle.getInt("hour");
int minute = bss.bundle.getInt("minute");
reminder = new Reminder(hour, minute, new WeekdayList(days));
setReminder(reminder);
}
super.onRestoreInstanceState(bss.getSuperState());
}
@Override
protected Parcelable onSaveInstanceState()
{
Parcelable superState = super.onSaveInstanceState();
Bundle bundle = new Bundle();
if (reminder != null)
{
bundle.putInt("days", reminder.getDays().toInteger());
bundle.putInt("hour", reminder.getHour());
bundle.putInt("minute", reminder.getMinute());
}
return new BundleSavedState(superState, bundle);
}
@OnClick(R.id.tvReminderTime)
void onDateSpinnerClick()
{
int hour = 8;
int min = 0;
if (reminder != null)
{
hour = reminder.getHour();
min = reminder.getMinute();
}
controller.onTimeClicked(hour, min);
}
@OnClick(R.id.tvReminderDays)
void onWeekdayClicked()
{
if (reminder == null) return;
controller.onWeekdayClicked(reminder.getDays());
}
public interface Controller
{
/**
* Called when the user has clicked the widget to change the time of
* the reminder.
*
* @param currentHour hour previously picked by the user
* @param currentMin minute previously picked by the user
*/
default void onTimeClicked(int currentHour, int currentMin) {}
/**
* Called when the used has clicked the widget to change the days
* of the reminder.
*
* @param currentDays days previously selected by the user.
*/
default void onWeekdayClicked(WeekdayList currentDays) {}
}
}

View File

@@ -1,91 +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.activities.habits.edit.views;
import android.content.*;
import android.content.res.*;
import android.support.annotation.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import org.isoron.uhabits.R;
import java.text.DecimalFormat;
import butterknife.*;
public class TargetPanel extends FrameLayout
{
private DecimalFormat valueFormatter = new DecimalFormat("#.##");
@BindView(R.id.tvUnit)
ExampleEditText tvUnit;
@BindView(R.id.tvTargetCount)
TextView tvTargetValue;
public TargetPanel(@NonNull Context context, @Nullable AttributeSet attrs)
{
super(context, attrs);
View view = inflate(context, R.layout.edit_habit_target, null);
ButterKnife.bind(this, view);
addView(view);
}
public double getTargetValue()
{
String sValue = tvTargetValue.getText().toString();
return Double.parseDouble(sValue);
}
public void setTargetValue(double targetValue)
{
tvTargetValue.setText(valueFormatter.format(targetValue));
}
public String getUnit()
{
return tvUnit.getRealText();
}
public void setUnit(String unit)
{
tvUnit.setRealText(unit);
}
public boolean validate()
{
Resources res = getResources();
String sValue = tvTargetValue.getText().toString();
double value = Double.parseDouble(sValue);
if (value <= 0)
{
tvTargetValue.setError(
res.getString(R.string.validation_number_should_be_positive));
return false;
}
return true;
}
}

View File

@@ -1,70 +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.activities.habits.list
import android.os.*
import org.isoron.uhabits.activities.*
import org.isoron.uhabits.activities.habits.list.views.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.ThemeSwitcher.*
import org.isoron.uhabits.core.utils.*
class ListHabitsActivity : HabitsActivity() {
var pureBlack: Boolean = false
lateinit var adapter: HabitCardListAdapter
lateinit var rootView: ListHabitsRootView
lateinit var screen: ListHabitsScreen
lateinit var prefs: Preferences
lateinit var midnightTimer: MidnightTimer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
prefs = appComponent.preferences
pureBlack = prefs.isPureBlackEnabled
midnightTimer = appComponent.midnightTimer
rootView = component.listHabitsRootView
screen = component.listHabitsScreen
adapter = component.habitCardListAdapter
setScreen(screen)
component.listHabitsBehavior.onStartup()
}
override fun onPause() {
midnightTimer.onPause()
screen.onDettached()
adapter.cancelRefresh()
super.onPause()
}
override fun onResume() {
adapter.refresh()
screen.onAttached()
rootView.postInvalidate()
midnightTimer.onResume()
if (prefs.theme == THEME_DARK && prefs.isPureBlackEnabled != pureBlack) {
restartWithFade(ListHabitsActivity::class.java)
}
super.onResume()
}
}

View File

@@ -1,111 +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.activities.habits.list
import android.view.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import javax.inject.*
@ActivityScope
class ListHabitsMenu @Inject constructor(
activity: BaseActivity,
private val preferences: Preferences,
private val themeSwitcher: ThemeSwitcher,
private val behavior: ListHabitsMenuBehavior
) : BaseMenu(activity) {
override fun onCreate(menu: Menu) {
val nightModeItem = menu.findItem(R.id.actionToggleNightMode)
val hideArchivedItem = menu.findItem(R.id.actionHideArchived)
val hideCompletedItem = menu.findItem(R.id.actionHideCompleted)
nightModeItem.isChecked = themeSwitcher.isNightMode
hideArchivedItem.isChecked = !preferences.showArchived
hideCompletedItem.isChecked = !preferences.showCompleted
}
override fun onItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.actionToggleNightMode -> {
behavior.onToggleNightMode()
return true
}
R.id.actionAdd -> {
behavior.onCreateHabit()
return true
}
R.id.actionFAQ -> {
behavior.onViewFAQ()
return true
}
R.id.actionAbout -> {
behavior.onViewAbout()
return true
}
R.id.actionSettings -> {
behavior.onViewSettings()
return true
}
R.id.actionHideArchived -> {
behavior.onToggleShowArchived()
invalidate()
return true
}
R.id.actionHideCompleted -> {
behavior.onToggleShowCompleted()
invalidate()
return true
}
R.id.actionSortColor -> {
behavior.onSortByColor()
return true
}
R.id.actionSortManual -> {
behavior.onSortByManually()
return true
}
R.id.actionSortName -> {
behavior.onSortByName()
return true
}
R.id.actionSortScore -> {
behavior.onSortByScore()
return true
}
else -> return false
}
}
override fun getMenuResourceId() = R.menu.list_habits
}

View File

@@ -1,58 +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.activities.habits.list
import android.content.*
import dagger.*
import org.isoron.androidbase.*
import org.isoron.uhabits.activities.*
import org.isoron.uhabits.activities.habits.list.views.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import javax.inject.*
class BugReporterProxy
@Inject constructor(
@AppContext context: Context
) : AndroidBugReporter(context), ListHabitsBehavior.BugReporter
@Module
abstract class ListHabitsModule {
@Binds
abstract fun getAdapter(adapter: HabitCardListAdapter): ListHabitsMenuBehavior.Adapter
@Binds
abstract fun getBugReporter(proxy: BugReporterProxy): ListHabitsBehavior.BugReporter
@Binds
abstract fun getMenuScreen(screen: ListHabitsScreen): ListHabitsMenuBehavior.Screen
@Binds
abstract fun getScreen(screen: ListHabitsScreen): ListHabitsBehavior.Screen
@Binds
abstract fun getSelMenuAdapter(adapter: HabitCardListAdapter): ListHabitsSelectionMenuBehavior.Adapter
@Binds
abstract fun getSelMenuScreen(screen: ListHabitsScreen): ListHabitsSelectionMenuBehavior.Screen
@Binds
abstract fun getSystem(system: HabitsDirFinder): ListHabitsBehavior.DirFinder
}

View File

@@ -1,135 +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.activities.habits.list
import android.content.*
import android.os.Build.VERSION.*
import android.os.Build.VERSION_CODES.*
import android.support.v7.widget.Toolbar
import android.view.ViewGroup.LayoutParams.*
import android.widget.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.common.views.*
import org.isoron.uhabits.activities.habits.list.views.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.tasks.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.utils.*
import java.lang.Math.*
import javax.inject.*
const val MAX_CHECKMARK_COUNT = 60
@ActivityScope
class ListHabitsRootView @Inject constructor(
@ActivityContext context: Context,
hintListFactory: HintListFactory,
preferences: Preferences,
midnightTimer: MidnightTimer,
runner: TaskRunner,
private val listAdapter: HabitCardListAdapter,
habitCardListViewFactory: HabitCardListViewFactory
) : BaseRootView(context), ModelObservable.Listener {
val listView: HabitCardListView = habitCardListViewFactory.create()
val llEmpty = EmptyListView(context)
val tbar = buildToolbar()
val progressBar = TaskProgressBar(context, runner)
val hintView: HintView
val header = HeaderView(context, preferences, midnightTimer)
init {
val hints = resources.getStringArray(R.array.hints)
val hintList = hintListFactory.create(hints)
hintView = HintView(context, hintList)
addView(RelativeLayout(context).apply {
background = sres.getDrawable(R.attr.windowBackgroundColor)
addAtTop(tbar)
addBelow(header, tbar)
addBelow(listView, header, height = MATCH_PARENT)
addBelow(llEmpty, header, height = MATCH_PARENT)
addBelow(progressBar, header) {
it.topMargin = dp(-6.0f).toInt()
}
addAtBottom(hintView)
if (SDK_INT < LOLLIPOP) {
addBelow(ShadowView(context), tbar)
addBelow(ShadowView(context), header)
}
}, MATCH_PARENT, MATCH_PARENT)
listAdapter.setListView(listView)
initToolbar()
}
override fun getToolbar(): Toolbar {
return tbar
}
override fun onModelChange() {
updateEmptyView()
}
private fun setupControllers() {
header.setScrollController(object : ScrollableChart.ScrollController {
override fun onDataOffsetChanged(newDataOffset: Int) {
listView.dataOffset = newDataOffset
}
})
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
setupControllers()
listAdapter.observable.addListener(this)
}
override fun onDetachedFromWindow() {
listAdapter.observable.removeListener(this)
super.onDetachedFromWindow()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
val count = getCheckmarkCount()
header.buttonCount = count
header.setMaxDataOffset(max(MAX_CHECKMARK_COUNT - count, 0))
listView.checkmarkCount = count
super.onSizeChanged(w, h, oldw, oldh)
}
private fun getCheckmarkCount(): Int {
val nameWidth = dim(R.dimen.habitNameWidth)
val buttonWidth = dim(R.dimen.checkmarkWidth)
val labelWidth = max((measuredWidth / 3).toFloat(), nameWidth)
val buttonCount = ((measuredWidth - labelWidth) / buttonWidth).toInt()
return min(MAX_CHECKMARK_COUNT, max(0, buttonCount))
}
private fun updateEmptyView() {
llEmpty.visibility = when (listAdapter.itemCount) {
0 -> VISIBLE
else -> GONE
}
}
}

View File

@@ -1,264 +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.activities.habits.list
import android.app.*
import android.content.*
import android.support.annotation.*
import dagger.*
import org.isoron.androidbase.activities.*
import org.isoron.androidbase.utils.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.common.dialogs.*
import org.isoron.uhabits.activities.habits.edit.*
import org.isoron.uhabits.activities.habits.list.views.*
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.tasks.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.core.ui.callbacks.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.*
import org.isoron.uhabits.intents.*
import org.isoron.uhabits.tasks.*
import java.io.*
import javax.inject.*
const val RESULT_IMPORT_DATA = 1
const val RESULT_EXPORT_CSV = 2
const val RESULT_EXPORT_DB = 3
const val RESULT_BUG_REPORT = 4
const val RESULT_REPAIR_DB = 5
const val REQUEST_OPEN_DOCUMENT = 6
const val REQUEST_SETTINGS = 7
@ActivityScope
class ListHabitsScreen
@Inject constructor(
activity: BaseActivity,
rootView: ListHabitsRootView,
private val commandRunner: CommandRunner,
private val intentFactory: IntentFactory,
private val themeSwitcher: ThemeSwitcher,
private val preferences: Preferences,
private val adapter: HabitCardListAdapter,
private val taskRunner: TaskRunner,
private val exportDBFactory: ExportDBTaskFactory,
private val importTaskFactory: ImportDataTaskFactory,
private val confirmDeleteDialogFactory: ConfirmDeleteDialogFactory,
private val colorPickerFactory: ColorPickerDialogFactory,
private val editHabitDialogFactory: EditHabitDialogFactory,
private val numberPickerFactory: NumberPickerFactory,
private val behavior: Lazy<ListHabitsBehavior>,
private val menu: Lazy<ListHabitsMenu>,
private val selectionMenu: Lazy<ListHabitsSelectionMenu>
) : BaseScreen(activity),
CommandRunner.Listener,
ListHabitsBehavior.Screen,
ListHabitsMenuBehavior.Screen,
ListHabitsSelectionMenuBehavior.Screen {
init {
setRootView(rootView)
}
fun onAttached() {
setMenu(menu.get())
setSelectionMenu(selectionMenu.get())
commandRunner.addListener(this)
}
fun onDettached() {
commandRunner.removeListener(this)
}
override fun onCommandExecuted(command: Command, refreshKey: Long?) {
if (command.isRemote) return
showMessage(getExecuteString(command))
}
override fun onResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_OPEN_DOCUMENT -> onOpenDocumentResult(resultCode, data)
REQUEST_SETTINGS -> onSettingsResult(resultCode)
}
}
private fun onOpenDocumentResult(resultCode: Int, data: Intent?) {
if (data == null) return
if (resultCode != Activity.RESULT_OK) return
try {
val inStream = activity.contentResolver.openInputStream(data.data)
val cacheDir = activity.externalCacheDir
val tempFile = File.createTempFile("import", "", cacheDir)
FileUtils.copy(inStream, tempFile)
onImportData(tempFile) { tempFile.delete() }
} catch (e: IOException) {
showMessage(R.string.could_not_import)
e.printStackTrace()
}
}
private fun onSettingsResult(resultCode: Int) {
when (resultCode) {
RESULT_IMPORT_DATA -> showImportScreen()
RESULT_EXPORT_CSV -> behavior.get().onExportCSV()
RESULT_EXPORT_DB -> onExportDB()
RESULT_BUG_REPORT -> behavior.get().onSendBugReport()
RESULT_REPAIR_DB -> behavior.get().onRepairDB()
}
}
override fun applyTheme() {
themeSwitcher.apply()
activity.restartWithFade(ListHabitsActivity::class.java)
}
override fun showAboutScreen() {
val intent = intentFactory.startAboutActivity(activity)
activity.startActivity(intent)
}
fun showCreateBooleanHabitScreen() {
val dialog = editHabitDialogFactory.createBoolean()
activity.showDialog(dialog, "editHabit")
}
override fun showCreateHabitScreen() {
if (!preferences.isNumericalHabitsFeatureEnabled) {
showCreateBooleanHabitScreen()
return
}
val dialog = AlertDialog.Builder(activity)
.setTitle("Type of habit")
.setItems(R.array.habitTypes) { _, which ->
if (which == 0) showCreateBooleanHabitScreen()
else showCreateNumericalHabitScreen()
}
.create()
dialog.show()
}
override fun showDeleteConfirmationScreen(callback: OnConfirmedCallback) {
activity.showDialog(confirmDeleteDialogFactory.create(callback))
}
override fun showEditHabitsScreen(habits: List<Habit>) {
val dialog = editHabitDialogFactory.edit(habits[0])
activity.showDialog(dialog, "editNumericalHabit")
}
override fun showFAQScreen() {
val intent = intentFactory.viewFAQ(activity)
activity.startActivity(intent)
}
override fun showHabitScreen(habit: Habit) {
val intent = intentFactory.startShowHabitActivity(activity, habit)
activity.startActivity(intent)
}
fun showImportScreen() {
val intent = intentFactory.openDocument()
activity.startActivityForResult(intent, REQUEST_OPEN_DOCUMENT)
}
override fun showIntroScreen() {
val intent = intentFactory.startIntroActivity(activity)
activity.startActivity(intent)
}
override fun showMessage(m: ListHabitsBehavior.Message) {
showMessage(when (m) {
COULD_NOT_EXPORT -> R.string.could_not_export
IMPORT_SUCCESSFUL -> R.string.habits_imported
IMPORT_FAILED -> R.string.could_not_import
DATABASE_REPAIRED -> R.string.database_repaired
COULD_NOT_GENERATE_BUG_REPORT -> R.string.bug_report_failed
FILE_NOT_RECOGNIZED -> R.string.file_not_recognized
})
}
override fun showSendBugReportToDeveloperScreen(log: String) {
val to = R.string.bugReportTo
val subject = R.string.bugReportSubject
showSendEmailScreen(to, subject, log)
}
override fun showSettingsScreen() {
val intent = intentFactory.startSettingsActivity(activity)
activity.startActivityForResult(intent, REQUEST_SETTINGS)
}
override fun showColorPicker(defaultColor: Int,
callback: OnColorPickedCallback) {
val picker = colorPickerFactory.create(defaultColor)
picker.setListener(callback)
activity.showDialog(picker, "picker")
}
override fun showNumberPicker(value: Double,
unit: String,
callback: ListHabitsBehavior.NumberPickerCallback) {
numberPickerFactory.create(value, unit, callback).show()
}
@StringRes
private fun getExecuteString(command: Command): Int? {
when (command) {
is ArchiveHabitsCommand -> return R.string.toast_habit_archived
is ChangeHabitColorCommand -> return R.string.toast_habit_changed
is CreateHabitCommand -> return R.string.toast_habit_created
is DeleteHabitsCommand -> return R.string.toast_habit_deleted
is EditHabitCommand -> return R.string.toast_habit_changed
is UnarchiveHabitsCommand -> return R.string.toast_habit_unarchived
else -> return null
}
}
private fun showCreateNumericalHabitScreen() {
val dialog = editHabitDialogFactory.createNumerical()
activity.showDialog(dialog, "editHabit")
}
private fun onImportData(file: File, onFinished: () -> Unit) {
taskRunner.execute(importTaskFactory.create(file) { result ->
if (result == ImportDataTask.SUCCESS) {
adapter.refresh()
showMessage(R.string.habits_imported)
} else if (result == ImportDataTask.NOT_RECOGNIZED) {
showMessage(R.string.file_not_recognized)
} else {
showMessage(R.string.could_not_import)
}
onFinished()
})
}
private fun onExportDB() {
taskRunner.execute(exportDBFactory.create { filename ->
if (filename != null) showSendFileScreen(filename)
else showMessage(R.string.could_not_export)
})
}
}

View File

@@ -1,108 +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.activities.habits.list
import android.view.*
import dagger.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.habits.list.views.*
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.core.utils.*
import javax.inject.*
@ActivityScope
class ListHabitsSelectionMenu @Inject constructor(
private val screen: ListHabitsScreen,
private val listAdapter: HabitCardListAdapter,
var commandRunner: CommandRunner,
private val prefs: Preferences,
private val behavior: ListHabitsSelectionMenuBehavior,
private val listController: Lazy<HabitCardListController>,
private val notificationTray: NotificationTray
) : BaseSelectionMenu() {
override fun onFinish() {
listController.get().onSelectionFinished()
super.onFinish()
}
override fun onItemClicked(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_edit_habit -> {
behavior.onEditHabits()
return true
}
R.id.action_archive_habit -> {
behavior.onArchiveHabits()
return true
}
R.id.action_unarchive_habit -> {
behavior.onUnarchiveHabits()
return true
}
R.id.action_delete -> {
behavior.onDeleteHabits()
return true
}
R.id.action_color -> {
behavior.onChangeColor()
return true
}
R.id.action_notify -> {
for(h in listAdapter.selected)
notificationTray.show(h, DateUtils.getToday(), 0)
return true
}
else -> return false
}
}
override fun onPrepare(menu: Menu): Boolean {
val itemEdit = menu.findItem(R.id.action_edit_habit)
val itemColor = menu.findItem(R.id.action_color)
val itemArchive = menu.findItem(R.id.action_archive_habit)
val itemUnarchive = menu.findItem(R.id.action_unarchive_habit)
val itemNotify = menu.findItem(R.id.action_notify)
itemColor.isVisible = true
itemEdit.isVisible = behavior.canEdit()
itemArchive.isVisible = behavior.canArchive()
itemUnarchive.isVisible = behavior.canUnarchive()
setTitle(Integer.toString(listAdapter.selected.size))
itemNotify.isVisible = prefs.isDeveloper
return true
}
fun onSelectionStart() = screen.startSelection()
fun onSelectionChange() = invalidate()
fun onSelectionFinish() = finish()
override fun getResourceId() = R.menu.list_habits_selection
}

View File

@@ -1,88 +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.activities.habits.list.views
import android.content.*
import android.view.*
import android.view.View.MeasureSpec.*
import android.widget.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.utils.*
abstract class ButtonPanelView<T : View>(
context: Context,
val preferences: Preferences
) : LinearLayout(context),
Preferences.Listener {
var buttonCount = 0
set(value) {
field = value
inflateButtons()
}
var dataOffset = 0
set(value) {
field = value
setupButtons()
}
var buttons = mutableListOf<T>()
override fun onCheckmarkSequenceChanged() {
inflateButtons()
}
@Synchronized
protected fun inflateButtons() {
val reverse = preferences.isCheckmarkSequenceReversed
buttons.clear()
repeat(buttonCount) { buttons.add(createButton()) }
removeAllViews()
if (reverse) buttons.reversed().forEach { addView(it) }
else buttons.forEach { addView(it) }
setupButtons()
requestLayout()
}
public override fun onAttachedToWindow() {
super.onAttachedToWindow()
preferences.addListener(this)
}
public override fun onDetachedFromWindow() {
preferences.removeListener(this)
super.onDetachedFromWindow()
}
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
val buttonWidth = dim(R.dimen.checkmarkWidth)
val buttonHeight = dim(R.dimen.checkmarkHeight)
val width = (buttonWidth * buttonCount)
super.onMeasure(width.toMeasureSpec(EXACTLY),
buttonHeight.toMeasureSpec(EXACTLY))
}
protected abstract fun setupButtons()
protected abstract fun createButton(): T
}

View File

@@ -1,123 +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.activities.habits.list.views
import android.content.*
import android.graphics.*
import android.text.*
import android.view.*
import android.view.View.MeasureSpec.*
import com.google.auto.factory.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.Checkmark.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.utils.*
@AutoFactory
class CheckmarkButtonView(
@Provided @ActivityContext context: Context,
@Provided val preferences: Preferences
) : View(context),
View.OnClickListener,
View.OnLongClickListener {
var color: Int = Color.BLACK
set(value) {
field = value
invalidate()
}
var value: Int = 0
set(value) {
field = value
invalidate()
}
var onToggle: () -> Unit = {}
private var drawer = Drawer()
init {
isFocusable = false
setOnClickListener(this)
setOnLongClickListener(this)
}
fun performToggle() {
onToggle()
value = when (value) {
CHECKED_EXPLICITLY -> UNCHECKED
else -> CHECKED_EXPLICITLY
}
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
invalidate()
}
override fun onClick(v: View) {
if (preferences.isShortToggleEnabled) performToggle()
else showMessage(R.string.long_press_to_toggle)
}
override fun onLongClick(v: View): Boolean {
performToggle()
return true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawer.draw(canvas)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val height = resources.getDimensionPixelSize(R.dimen.checkmarkHeight)
val width = resources.getDimensionPixelSize(R.dimen.checkmarkWidth)
super.onMeasure(width.toMeasureSpec(EXACTLY),
height.toMeasureSpec(EXACTLY))
}
private inner class Drawer {
private val rect = RectF()
private val lowContrastColor = sres.getColor(R.attr.lowContrastTextColor)
private val paint = TextPaint().apply {
typeface = getFontAwesome()
isAntiAlias = true
textAlign = Paint.Align.CENTER
textSize = dim(R.dimen.smallTextSize)
}
fun draw(canvas: Canvas) {
paint.color = when (value) {
CHECKED_EXPLICITLY -> color
else -> lowContrastColor
}
val id = when (value) {
UNCHECKED -> R.string.fa_times
else -> R.string.fa_check
}
val label = resources.getString(id)
val em = paint.measureText("m")
rect.set(0f, 0f, width.toFloat(), height.toFloat())
rect.offset(0f, 0.4f * em)
canvas.drawText(label, rect.centerX(), rect.centerY(), paint)
}
}
}

View File

@@ -1,71 +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.activities.habits.list.views
import android.content.*
import com.google.auto.factory.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.Checkmark.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.utils.*
@AutoFactory
class CheckmarkPanelView(
@Provided @ActivityContext context: Context,
@Provided preferences: Preferences,
@Provided private val buttonFactory: CheckmarkButtonViewFactory
) : ButtonPanelView<CheckmarkButtonView>(context, preferences) {
var values = IntArray(0)
set(values) {
field = values
setupButtons()
}
var color = 0
set(value) {
field = value
setupButtons()
}
var onToggle: (Timestamp) -> Unit = {}
set(value) {
field = value
setupButtons()
}
override fun createButton(): CheckmarkButtonView = buttonFactory.create()
@Synchronized
override fun setupButtons() {
val today = DateUtils.getToday()
buttons.forEachIndexed { index, button ->
val timestamp = today.minus(index + dataOffset)
button.value = when {
index + dataOffset < values.size -> values[index + dataOffset]
else -> UNCHECKED
}
button.color = color
button.onToggle = { onToggle(timestamp) }
}
}
}

View File

@@ -1,52 +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.activities.habits.list.views
import android.content.*
import android.view.*
import android.view.Gravity.*
import android.view.ViewGroup.LayoutParams.*
import android.widget.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.*
import org.isoron.uhabits.utils.*
class EmptyListView(context: Context) : LinearLayout(context) {
init {
orientation = VERTICAL
gravity = Gravity.CENTER
visibility = BaseRootView.GONE
addView(TextView(context).apply {
text = str(R.string.fa_star_half_o)
typeface = getFontAwesome()
textSize = sp(40.0f)
gravity = CENTER
setTextColor(sres.getColor(R.attr.mediumContrastTextColor))
}, MATCH_PARENT, WRAP_CONTENT)
addView(TextView(context).apply {
text = str(R.string.no_habits_found)
gravity = CENTER
setPadding(0, dp(20.0f).toInt(), 0, 0)
setTextColor(sres.getColor(R.attr.mediumContrastTextColor))
}, MATCH_PARENT, WRAP_CONTENT)
}
}

View File

@@ -1,334 +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.activities.habits.list.views;
import android.support.annotation.*;
import android.support.v7.widget.*;
import android.view.*;
import org.isoron.androidbase.activities.*;
import org.isoron.uhabits.activities.habits.list.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.ui.screens.habits.list.*;
import org.isoron.uhabits.core.utils.*;
import java.util.*;
import javax.inject.*;
/**
* Provides data that backs a {@link HabitCardListView}.
* <p>
* The data if fetched and cached by a {@link HabitCardListCache}. This adapter
* also holds a list of items that have been selected.
*/
@ActivityScope
public class HabitCardListAdapter
extends RecyclerView.Adapter<HabitCardViewHolder> implements
HabitCardListCache.Listener,
MidnightTimer.MidnightListener,
ListHabitsMenuBehavior.Adapter,
ListHabitsSelectionMenuBehavior.Adapter
{
@NonNull
private ModelObservable observable;
@Nullable
private HabitCardListView listView;
@NonNull
private final LinkedList<Habit> selected;
@NonNull
private final HabitCardListCache cache;
@NonNull
private Preferences preferences;
private final MidnightTimer midnightTimer;
@Inject
public HabitCardListAdapter(@NonNull HabitCardListCache cache,
@NonNull Preferences preferences,
@NonNull MidnightTimer midnightTimer)
{
this.preferences = preferences;
this.selected = new LinkedList<>();
this.observable = new ModelObservable();
this.cache = cache;
this.midnightTimer = midnightTimer;
cache.setListener(this);
cache.setCheckmarkCount(
ListHabitsRootViewKt.MAX_CHECKMARK_COUNT);
cache.setOrder(preferences.getDefaultOrder());
setHasStableIds(true);
}
@Override
public void atMidnight()
{
cache.refreshAllHabits();
}
public void cancelRefresh()
{
cache.cancelTasks();
}
/**
* Sets all items as not selected.
*/
@Override
public void clearSelection()
{
selected.clear();
notifyDataSetChanged();
observable.notifyListeners();
}
/**
* Returns the item that occupies a certain position on the list
*
* @param position position of the item
* @return the item at given position or null if position is invalid
*/
@Deprecated
@Nullable
public Habit getItem(int position)
{
return cache.getHabitByPosition(position);
}
@Override
public int getItemCount()
{
return cache.getHabitCount();
}
@Override
public long getItemId(int position)
{
return getItem(position).getId();
}
@NonNull
public ModelObservable getObservable()
{
return observable;
}
@Override
@NonNull
public List<Habit> getSelected()
{
return new LinkedList<>(selected);
}
/**
* Returns whether list of selected items is empty.
*
* @return true if selection is empty, false otherwise
*/
public boolean isSelectionEmpty()
{
return selected.isEmpty();
}
public boolean isSortable()
{
return cache.getOrder() == HabitList.Order.BY_POSITION;
}
/**
* Notify the adapter that it has been attached to a ListView.
*/
public void onAttached()
{
cache.onAttached();
midnightTimer.addListener(this);
}
@Override
public void onBindViewHolder(@Nullable HabitCardViewHolder holder,
int position)
{
if (holder == null) return;
if (listView == null) return;
Habit habit = cache.getHabitByPosition(position);
double score = cache.getScore(habit.getId());
int checkmarks[] = cache.getCheckmarks(habit.getId());
boolean selected = this.selected.contains(habit);
listView.bindCardView(holder, habit, score, checkmarks, selected);
}
@Override
public void onViewAttachedToWindow(@Nullable HabitCardViewHolder holder)
{
if (listView == null) return;
listView.attachCardView(holder);
}
@Override
public void onViewDetachedFromWindow(@Nullable HabitCardViewHolder holder)
{
if (listView == null) return;
listView.detachCardView(holder);
}
@Override
public HabitCardViewHolder onCreateViewHolder(ViewGroup parent,
int viewType)
{
if (listView == null) return null;
View view = listView.createHabitCardView();
return new HabitCardViewHolder(view);
}
/**
* Notify the adapter that it has been detached from a ListView.
*/
public void onDetached()
{
cache.onDetached();
midnightTimer.removeListener(this);
}
@Override
public void onItemChanged(int position)
{
notifyItemChanged(position);
observable.notifyListeners();
}
@Override
public void onItemInserted(int position)
{
notifyItemInserted(position);
observable.notifyListeners();
}
@Override
public void onItemMoved(int fromPosition, int toPosition)
{
notifyItemMoved(fromPosition, toPosition);
observable.notifyListeners();
}
@Override
public void onItemRemoved(int position)
{
notifyItemRemoved(position);
observable.notifyListeners();
}
@Override
public void onRefreshFinished()
{
observable.notifyListeners();
}
/**
* Removes a list of habits from the adapter.
* <p>
* Note that this only has effect on the adapter cache. The database is not
* modified, and the change is lost when the cache is refreshed. This method
* is useful for making the ListView more responsive: while we wait for the
* database operation to finish, the cache can be modified to reflect the
* changes immediately.
*
* @param habits list of habits to be removed
*/
@Override
public void performRemove(List<Habit> habits)
{
for (Habit h : habits)
cache.remove(h.getId());
}
/**
* Changes the order of habits on the adapter.
* <p>
* Note that this only has effect on the adapter cache. The database is not
* modified, and the change is lost when the cache is refreshed. This method
* is useful for making the ListView more responsive: while we wait for the
* database operation to finish, the cache can be modified to reflect the
* changes immediately.
*
* @param from the habit that should be moved
* @param to the habit that currently occupies the desired position
*/
public void performReorder(int from, int to)
{
cache.reorder(from, to);
}
@Override
public void refresh()
{
cache.refreshAllHabits();
}
@Override
public void setFilter(HabitMatcher matcher)
{
cache.setFilter(matcher);
}
/**
* Sets the HabitCardListView that this adapter will provide data for.
* <p>
* This object will be used to generated new HabitCardViews, upon demand.
*
* @param listView the HabitCardListView associated with this adapter
*/
public void setListView(@Nullable HabitCardListView listView)
{
this.listView = listView;
}
@Override
public void setOrder(HabitList.Order order)
{
cache.setOrder(order);
preferences.setDefaultOrder(order);
}
/**
* Selects or deselects the item at a given position.
*
* @param position position of the item to be toggled
*/
public void toggleSelection(int position)
{
Habit h = getItem(position);
if (h == null) return;
int k = selected.indexOf(h);
if (k < 0) selected.add(h);
else selected.remove(h);
notifyDataSetChanged();
}
}

View File

@@ -1,167 +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.activities.habits.list.views
import dagger.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.activities.habits.list.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import javax.inject.*
/**
* Controller responsible for receiving and processing the events generated by a
* HabitListView. These include selecting and reordering items, toggling
* checkmarks and clicking habits.
*/
@ActivityScope
class HabitCardListController @Inject constructor(
private val adapter: HabitCardListAdapter,
private val behavior: ListHabitsBehavior,
private val selectionMenu: Lazy<ListHabitsSelectionMenu>
) : HabitCardListView.Controller, ModelObservable.Listener {
private val NORMAL_MODE = NormalMode()
private val SELECTION_MODE = SelectionMode()
private var activeMode: Mode
init {
this.activeMode = NORMAL_MODE
adapter.observable.addListener(this)
}
override fun drop(from: Int, to: Int) {
if (from == to) return
cancelSelection()
val habitFrom = adapter.getItem(from)
val habitTo = adapter.getItem(to)
if(habitFrom == null || habitTo == null) return
adapter.performReorder(from, to)
behavior.onReorderHabit(habitFrom, habitTo)
}
override fun onItemClick(position: Int) {
activeMode.onItemClick(position)
}
override fun onItemLongClick(position: Int) {
activeMode.onItemLongClick(position)
}
override fun onModelChange() {
if (adapter.isSelectionEmpty) {
activeMode = NormalMode()
selectionMenu.get().onSelectionFinish()
}
}
fun onSelectionFinished() {
cancelSelection()
}
override fun startDrag(position: Int) {
activeMode.startDrag(position)
}
protected fun toggleSelection(position: Int) {
adapter.toggleSelection(position)
activeMode = if (adapter.isSelectionEmpty) NORMAL_MODE else SELECTION_MODE
}
private fun cancelSelection() {
adapter.clearSelection()
activeMode = NormalMode()
selectionMenu.get().onSelectionFinish()
}
interface HabitListener {
fun onHabitClick(habit: Habit)
fun onHabitReorder(from: Habit, to: Habit)
}
/**
* A Mode describes the behavior of the list upon clicking, long clicking
* and dragging an item. This depends on whether some items are already
* selected or not.
*/
private interface Mode {
fun onItemClick(position: Int)
fun onItemLongClick(position: Int): Boolean
fun startDrag(position: Int)
}
/**
* Mode activated when there are no items selected. Clicks trigger habit
* click. Long clicks start selection.
*/
internal inner class NormalMode : Mode {
override fun onItemClick(position: Int) {
val habit = adapter.getItem(position)
if (habit == null) return
behavior.onClickHabit(habit)
}
override fun onItemLongClick(position: Int): Boolean {
startSelection(position)
return true
}
override fun startDrag(position: Int) {
startSelection(position)
}
protected fun startSelection(position: Int) {
toggleSelection(position)
activeMode = SELECTION_MODE
selectionMenu.get().onSelectionStart()
}
}
/**
* Mode activated when some items are already selected. Clicks toggle
* item selection. Long clicks select more items.
*/
internal inner class SelectionMode : Mode {
override fun onItemClick(position: Int) {
toggleSelection(position)
notifyListener()
}
override fun onItemLongClick(position: Int): Boolean {
toggleSelection(position)
notifyListener()
return true
}
override fun startDrag(position: Int) {
toggleSelection(position)
notifyListener()
}
protected fun notifyListener() {
if (activeMode === SELECTION_MODE)
selectionMenu.get().onSelectionChange()
else
selectionMenu.get().onSelectionFinish()
}
}
}

View File

@@ -1,172 +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.activities.habits.list.views
import android.content.*
import android.os.*
import android.support.v7.widget.*
import android.support.v7.widget.helper.*
import android.support.v7.widget.helper.ItemTouchHelper.*
import android.view.*
import com.google.auto.factory.*
import dagger.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.common.views.*
import org.isoron.uhabits.core.models.*
@AutoFactory
class HabitCardListView(
@Provided @ActivityContext context: Context,
@Provided private val adapter: HabitCardListAdapter,
@Provided private val cardViewFactory: HabitCardViewFactory,
@Provided private val controller: Lazy<HabitCardListController>
) : RecyclerView(context, null, R.attr.scrollableRecyclerViewStyle) {
var checkmarkCount: Int = 0
var dataOffset: Int = 0
set(value) {
field = value
attachedHolders
.map { it.itemView as HabitCardView }
.forEach { it.dataOffset = value }
}
private val attachedHolders = mutableListOf<HabitCardViewHolder>()
private val touchHelper = ItemTouchHelper(TouchHelperCallback()).apply {
attachToRecyclerView(this@HabitCardListView)
}
init {
setHasFixedSize(true)
isLongClickable = true
layoutManager = LinearLayoutManager(context)
super.setAdapter(adapter)
}
fun createHabitCardView(): View {
return cardViewFactory.create()
}
fun bindCardView(holder: HabitCardViewHolder,
habit: Habit,
score: Double,
checkmarks: IntArray,
selected: Boolean): View {
val cardView = holder.itemView as HabitCardView
cardView.habit = habit
cardView.isSelected = selected
cardView.values = checkmarks
cardView.buttonCount = checkmarkCount
cardView.dataOffset = dataOffset
cardView.score = score
cardView.unit = habit.unit
cardView.threshold = habit.targetValue
val detector = GestureDetector(context, CardViewGestureDetector(holder))
cardView.setOnTouchListener { _, ev ->
detector.onTouchEvent(ev)
true
}
return cardView
}
fun attachCardView(holder: HabitCardViewHolder) {
attachedHolders.add(holder)
}
fun detachCardView(holder: HabitCardViewHolder) {
attachedHolders.remove(holder)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
adapter.onAttached()
}
override fun onDetachedFromWindow() {
adapter.onDetached()
super.onDetachedFromWindow()
}
override fun onRestoreInstanceState(state: Parcelable) {
if (state !is BundleSavedState) {
super.onRestoreInstanceState(state)
return
}
dataOffset = state.bundle.getInt("dataOffset")
super.onRestoreInstanceState(state.superState)
}
override fun onSaveInstanceState(): Parcelable {
val superState = super.onSaveInstanceState()
val bundle = Bundle().apply {
putInt("dataOffset", dataOffset)
}
return BundleSavedState(superState, bundle)
}
interface Controller {
fun drop(from: Int, to: Int) {}
fun onItemClick(pos: Int) {}
fun onItemLongClick(pos: Int) {}
fun startDrag(position: Int) {}
}
private inner class CardViewGestureDetector(
private val holder: HabitCardViewHolder
) : GestureDetector.SimpleOnGestureListener() {
override fun onLongPress(e: MotionEvent) {
val position = holder.adapterPosition
controller.get().onItemLongClick(position)
if (adapter.isSortable) touchHelper.startDrag(holder)
}
override fun onSingleTapUp(e: MotionEvent): Boolean {
val position = holder.adapterPosition
controller.get().onItemClick(position)
return true
}
}
inner class TouchHelperCallback : ItemTouchHelper.Callback() {
override fun getMovementFlags(recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder): Int {
return makeMovementFlags(UP or DOWN, START or END)
}
override fun onMove(recyclerView: RecyclerView,
from: RecyclerView.ViewHolder,
to: RecyclerView.ViewHolder): Boolean {
controller.get().drop(from.adapterPosition, to.adapterPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder,
direction: Int) {
}
override fun isItemViewSwipeEnabled() = false
override fun isLongPressDragEnabled() = false
}
}

View File

@@ -1,252 +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.activities.habits.list.views
import android.content.*
import android.os.*
import android.os.Build.VERSION.*
import android.os.Build.VERSION_CODES.*
import android.text.*
import android.view.*
import android.view.ViewGroup.LayoutParams.*
import android.widget.*
import com.google.auto.factory.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.common.views.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.utils.*
@AutoFactory
class HabitCardView(
@Provided @ActivityContext context: Context,
@Provided private val checkmarkPanelFactory: CheckmarkPanelViewFactory,
@Provided private val numberPanelFactory: NumberPanelViewFactory,
@Provided private val behavior: ListHabitsBehavior
) : FrameLayout(context),
ModelObservable.Listener {
var buttonCount
get() = checkmarkPanel.buttonCount
set(value) {
checkmarkPanel.buttonCount = value
numberPanel.buttonCount = value
}
var dataOffset = 0
set(value) {
field = value
checkmarkPanel.dataOffset = value
numberPanel.dataOffset = value
}
var habit: Habit? = null
set(newHabit) {
if (isAttachedToWindow) {
field?.observable?.removeListener(this)
newHabit?.observable?.addListener(this)
}
field = newHabit
if (newHabit != null) copyAttributesFrom(newHabit)
}
var score
get() = scoreRing.percentage.toDouble()
set(value) {
scoreRing.percentage = value.toFloat()
scoreRing.precision = 1.0f / 16
}
var unit
get() = numberPanel.units
set(value) {
numberPanel.units = value
}
var values
get() = checkmarkPanel.values
set(values) {
checkmarkPanel.values = values
numberPanel.values = values.map { it / 1000.0 }.toDoubleArray()
}
var threshold: Double
get() = numberPanel.threshold
set(value) {
numberPanel.threshold = value
}
private var checkmarkPanel: CheckmarkPanelView
private var numberPanel: NumberPanelView
private var innerFrame: LinearLayout
private var label: TextView
private var scoreRing: RingView
init {
scoreRing = RingView(context).apply {
val thickness = dp(3f)
val margin = dp(8f).toInt()
val ringSize = dp(15f).toInt()
layoutParams = LinearLayout.LayoutParams(ringSize, ringSize).apply {
setMargins(margin, 0, margin, 0)
gravity = Gravity.CENTER
}
setThickness(thickness)
}
label = TextView(context).apply {
maxLines = 2
ellipsize = TextUtils.TruncateAt.END
layoutParams = LinearLayout.LayoutParams(0, WRAP_CONTENT, 1f)
if (SDK_INT >= M) breakStrategy = Layout.BREAK_STRATEGY_BALANCED
}
checkmarkPanel = checkmarkPanelFactory.create().apply {
onToggle = { timestamp ->
triggerRipple(timestamp)
habit?.let { behavior.onToggle(it, timestamp) }
}
}
numberPanel = numberPanelFactory.create().apply {
visibility = GONE
onEdit = { timestamp ->
triggerRipple(timestamp)
habit?.let { behavior.onEdit(it, timestamp) }
}
}
innerFrame = LinearLayout(context).apply {
gravity = Gravity.CENTER_VERTICAL
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
if (SDK_INT >= LOLLIPOP) elevation = dp(1f)
addView(scoreRing)
addView(label)
addView(checkmarkPanel)
addView(numberPanel)
setOnTouchListener { v, event ->
if (SDK_INT >= LOLLIPOP)
v.background.setHotspot(event.x, event.y)
false
}
}
clipToPadding = false
layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
val margin = dp(3f).toInt()
setPadding(margin, 0, margin, margin)
addView(innerFrame)
}
override fun onModelChange() {
Handler(Looper.getMainLooper()).post {
habit?.let { copyAttributesFrom(it) }
}
}
override fun setSelected(isSelected: Boolean) {
super.setSelected(isSelected)
updateBackground(isSelected)
}
fun triggerRipple(timestamp: Timestamp) {
val today = DateUtils.getToday()
val offset = timestamp.daysUntil(today) - dataOffset
val button = checkmarkPanel.buttons[offset]
val y = button.height / 2.0f
val x = checkmarkPanel.x + button.x + (button.width / 2).toFloat()
triggerRipple(x, y)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
habit?.observable?.addListener(this)
}
override fun onDetachedFromWindow() {
habit?.observable?.removeListener(this)
super.onDetachedFromWindow()
}
private fun copyAttributesFrom(h: Habit) {
fun getActiveColor(habit: Habit): Int {
return when (habit.isArchived) {
true -> sres.getColor(R.attr.mediumContrastTextColor)
false -> PaletteUtils.getColor(context, habit.color)
}
}
val c = getActiveColor(h)
label.apply {
text = h.name
setTextColor(c)
}
scoreRing.apply {
color = c
}
checkmarkPanel.apply {
color = c
visibility = when (h.isNumerical) {
true -> View.GONE
false -> View.VISIBLE
}
}
numberPanel.apply {
color = c
units = h.unit
threshold = h.targetValue
visibility = when (h.isNumerical) {
true -> View.VISIBLE
false -> View.GONE
}
}
}
private fun triggerRipple(x: Float, y: Float) {
val background = innerFrame.background
if (SDK_INT >= LOLLIPOP) background.setHotspot(x, y)
background.state = intArrayOf(android.R.attr.state_pressed,
android.R.attr.state_enabled)
Handler().postDelayed({ background.state = intArrayOf() }, 25)
}
private fun updateBackground(isSelected: Boolean) {
if (SDK_INT < LOLLIPOP) {
val background = when (isSelected) {
true -> sres.getDrawable(R.attr.selectedBackground)
false -> sres.getDrawable(R.attr.cardBackground)
}
innerFrame.setBackgroundDrawable(background)
return
}
val background = when (isSelected) {
true -> R.drawable.selected_box
false -> R.drawable.ripple
}
innerFrame.setBackgroundResource(background)
}
}

View File

@@ -1,25 +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.activities.habits.list.views
import android.support.v7.widget.*
import android.view.*
class HabitCardViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

View File

@@ -1,136 +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.activities.habits.list.views
import android.content.*
import android.graphics.*
import android.os.Build.VERSION.*
import android.os.Build.VERSION_CODES.*
import android.text.*
import android.view.View.MeasureSpec.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.common.views.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.core.utils.DateUtils.*
import org.isoron.uhabits.utils.*
import java.util.*
class HeaderView(
context: Context,
val prefs: Preferences,
val midnightTimer: MidnightTimer
) : ScrollableChart(context),
Preferences.Listener,
MidnightTimer.MidnightListener {
private var drawer = Drawer()
var buttonCount: Int = 0
set(value) {
field = value
requestLayout()
}
init {
setScrollerBucketSize(dim(R.dimen.checkmarkWidth).toInt())
setBackgroundColor(sres.getColor(R.attr.headerBackgroundColor))
if (SDK_INT >= LOLLIPOP) elevation = dp(2.0f)
}
override fun atMidnight() {
post { invalidate() }
}
override fun onCheckmarkSequenceChanged() {
updateScrollDirection()
postInvalidate()
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
updateScrollDirection()
prefs.addListener(this)
midnightTimer.addListener(this)
}
override fun onDetachedFromWindow() {
midnightTimer.removeListener(this)
prefs.removeListener(this)
super.onDetachedFromWindow()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawer.draw(canvas)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val height = dim(R.dimen.checkmarkHeight)
setMeasuredDimension(widthMeasureSpec, height.toMeasureSpec(EXACTLY))
}
private fun updateScrollDirection() {
var direction = -1
if (prefs.isCheckmarkSequenceReversed) direction *= -1
if (isRTL()) direction *= -1
setScrollDirection(direction)
}
private inner class Drawer {
private val rect = RectF()
private val paint = TextPaint().apply {
color = Color.BLACK
isAntiAlias = true
textSize = dim(R.dimen.tinyTextSize)
textAlign = Paint.Align.CENTER
typeface = Typeface.DEFAULT_BOLD
color = sres.getColor(R.attr.mediumContrastTextColor)
}
fun draw(canvas: Canvas) {
val day = getStartOfTodayCalendar()
val width = dim(R.dimen.checkmarkWidth)
val height = dim(R.dimen.checkmarkHeight)
val isReversed = prefs.isCheckmarkSequenceReversed
day.add(GregorianCalendar.DAY_OF_MONTH, -dataOffset)
val em = paint.measureText("m")
repeat(buttonCount) { index ->
rect.set(0f, 0f, width, height)
rect.offset(canvas.width.toFloat(), 0f)
if (isReversed) rect.offset(-(index + 1) * width, 0f)
else rect.offset((index - buttonCount) * width, 0f)
if (isRTL()) rect.set(canvas.width - rect.right, rect.top,
canvas.width - rect.left, rect.bottom)
val y1 = rect.centerY() - 0.25 * em
val y2 = rect.centerY() + 1.25 * em
val lines = formatHeaderDate(day).toUpperCase().split("\n")
canvas.drawText(lines[0], rect.centerX(), y1.toFloat(), paint)
canvas.drawText(lines[1], rect.centerX(), y2.toFloat(), paint)
day.add(GregorianCalendar.DAY_OF_MONTH, -1)
}
}
}
}

View File

@@ -1,91 +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.activities.habits.list.views
import android.animation.*
import android.content.*
import android.graphics.*
import android.graphics.Color.*
import android.view.*
import android.view.ViewGroup.LayoutParams.*
import android.widget.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.utils.*
class HintView(
context: Context,
private val hintList: HintList
) : LinearLayout(context) {
val hintContent: TextView
init {
isClickable = true
visibility = GONE
orientation = VERTICAL
val p1 = dp(16.0f).toInt()
val p2 = dp(4.0f).toInt()
setPadding(p1, p1, p2, p1)
setBackgroundColor(resources.getColor(R.color.indigo_500))
val hintTitle = TextView(context).apply {
setTextColor(WHITE)
setTypeface(null, Typeface.BOLD)
text = resources.getString(R.string.hint_title)
}
hintContent = TextView(context).apply {
setTextColor(WHITE)
setPadding(0, dp(5.0f).toInt(), 0, 0)
}
addView(hintTitle, WRAP_CONTENT, WRAP_CONTENT)
addView(hintContent, WRAP_CONTENT, WRAP_CONTENT)
setOnClickListener { dismiss() }
}
public override fun onAttachedToWindow() {
super.onAttachedToWindow()
showNext()
}
fun showNext() {
if (!hintList.shouldShow()) return
val hint = hintList.pop() ?: return
hintContent.text = hint
requestLayout()
alpha = 0.0f
visibility = View.VISIBLE
animate().alpha(1f).duration = 500
}
private fun dismiss() {
animate().alpha(0f).setDuration(500).setListener(DismissAnimator())
}
private inner class DismissAnimator : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: android.animation.Animator) {
visibility = View.GONE
}
}
}

View File

@@ -1,160 +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.activities.habits.list.views
import android.content.*
import android.graphics.*
import android.text.*
import android.view.*
import android.view.View.*
import com.google.auto.factory.*
import org.isoron.androidbase.activities.*
import org.isoron.androidbase.utils.*
import org.isoron.androidbase.utils.InterfaceUtils.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.utils.*
import java.text.*
private val BOLD_TYPEFACE = Typeface.create("sans-serif-condensed", Typeface.BOLD)
private val NORMAL_TYPEFACE = Typeface.create("sans-serif-condensed", Typeface.NORMAL)
fun Double.toShortString(): String = when {
this >= 1e9 -> String.format("%.1fG", this / 1e9)
this >= 1e8 -> String.format("%.0fM", this / 1e6)
this >= 1e7 -> String.format("%.1fM", this / 1e6)
this >= 1e6 -> String.format("%.1fM", this / 1e6)
this >= 1e5 -> String.format("%.0fk", this / 1e3)
this >= 1e4 -> String.format("%.1fk", this / 1e3)
this >= 1e3 -> String.format("%.1fk", this / 1e3)
this >= 1e2 -> DecimalFormat("#").format(this)
this >= 1e1 -> DecimalFormat("#.#").format(this)
else -> DecimalFormat("#.##").format(this)
}
@AutoFactory
class NumberButtonView(
@Provided @ActivityContext context: Context,
@Provided val preferences: Preferences
) : View(context),
OnClickListener,
OnLongClickListener {
var color = 0
set(value) {
field = value
invalidate()
}
var value = 0.0
set(value) {
field = value
invalidate()
}
var threshold = 0.0
set(value) {
field = value
invalidate()
}
var units = ""
set(value) {
field = value
invalidate()
}
var onEdit: () -> Unit = {}
private var drawer: Drawer = Drawer(context)
init {
setOnClickListener(this)
setOnLongClickListener(this)
}
override fun onClick(v: View) {
if (preferences.isShortToggleEnabled) onEdit()
else showMessage(R.string.long_press_to_edit)
}
override fun onLongClick(v: View): Boolean {
onEdit()
return true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawer.draw(canvas)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val width = getDimension(context, R.dimen.checkmarkWidth).toInt()
val height = getDimension(context, R.dimen.checkmarkHeight).toInt()
setMeasuredDimension(width, height)
}
private inner class Drawer(context: Context) {
private val em: Float
private val rect: RectF = RectF()
private val sr = StyledResources(context)
private val lightGrey: Int
private val darkGrey: Int
private val pRegular: TextPaint = TextPaint().apply {
textSize = getDimension(context, R.dimen.smallerTextSize)
typeface = NORMAL_TYPEFACE
isAntiAlias = true
textAlign = Paint.Align.CENTER
}
private val pBold: TextPaint = TextPaint().apply {
textSize = getDimension(context, R.dimen.smallTextSize)
typeface = BOLD_TYPEFACE
isAntiAlias = true
textAlign = Paint.Align.CENTER
}
init {
em = pBold.measureText("m")
lightGrey = sr.getColor(R.attr.lowContrastTextColor)
darkGrey = sr.getColor(R.attr.mediumContrastTextColor)
}
fun draw(canvas: Canvas) {
val activeColor = when {
value == 0.0 -> lightGrey
value < threshold -> darkGrey
else -> color
}
val label = value.toShortString()
pBold.color = activeColor
pRegular.color = activeColor
rect.set(0f, 0f, width.toFloat(), height.toFloat())
canvas.drawText(label, rect.centerX(), rect.centerY(), pBold)
rect.offset(0f, 1.2f * em)
canvas.drawText(units, rect.centerX(), rect.centerY(), pRegular)
}
}
}

View File

@@ -1,84 +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.activities.habits.list.views
import android.content.*
import com.google.auto.factory.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.utils.*
@AutoFactory
class NumberPanelView(
@Provided @ActivityContext context: Context,
@Provided preferences: Preferences,
@Provided private val buttonFactory: NumberButtonViewFactory
) : ButtonPanelView<NumberButtonView>(context, preferences) {
var values = DoubleArray(0)
set(values) {
field = values
setupButtons()
}
var threshold = 0.0
set(value) {
field = value
setupButtons()
}
var color = 0
set(value) {
field = value
setupButtons()
}
var units = ""
set(value) {
field = value
setupButtons()
}
var onEdit: (Timestamp) -> Unit = {}
set(value) {
field = value
setupButtons()
}
override fun createButton() = buttonFactory.create()!!
@Synchronized
override fun setupButtons() {
val today = DateUtils.getToday()
buttons.forEachIndexed { index, button ->
val timestamp = today.minus(index + dataOffset)
button.value = when {
index + dataOffset < values.size -> values[index + dataOffset]
else -> 0.0
}
button.color = color
button.threshold = threshold
button.units = units
button.onEdit = { onEdit(timestamp) }
}
}
}

View File

@@ -1,39 +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.activities.habits.list.views
import android.content.*
import android.view.*
import android.view.View.MeasureSpec.*
import org.isoron.uhabits.*
import org.isoron.uhabits.utils.*
@Suppress("DEPRECATION")
class ShadowView(context: Context) : View(context) {
init {
alpha = 0.2f
background = resources.getDrawable(R.drawable.shadow)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec,
dp(2.0f).toInt().toMeasureSpec(EXACTLY))
}
}

View File

@@ -1,39 +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.activities.habits.show;
import android.os.*;
import org.isoron.uhabits.activities.*;
/**
* Activity that allows the user to see more information about a single habit.
* <p>
* Shows all the metadata for the habit, in addition to several charts.
*/
public class ShowHabitActivity extends HabitsActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setScreen(getComponent().getShowHabitScreen());
}
}

View File

@@ -1,38 +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.activities.habits.show;
import org.isoron.uhabits.activities.*;
import org.isoron.uhabits.core.ui.screens.habits.show.*;
import dagger.*;
@Module
public abstract class ShowHabitModule
{
@Binds
abstract ShowHabitBehavior.Screen getScreen(ShowHabitScreen screen);
@Binds
abstract ShowHabitMenuBehavior.Screen getMenuScreen(ShowHabitScreen screen);
@Binds
abstract ShowHabitMenuBehavior.System getSystem(HabitsDirFinder system);
}

View File

@@ -1,150 +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.activities.habits.show;
import android.content.*;
import android.os.*;
import android.support.annotation.*;
import android.support.v7.widget.*;
import org.isoron.androidbase.activities.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.habits.show.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.utils.*;
import javax.inject.*;
import butterknife.*;
@ActivityScope
public class ShowHabitRootView extends BaseRootView
implements ModelObservable.Listener
{
@NonNull
private Habit habit;
@BindView(R.id.frequencyCard)
FrequencyCard frequencyCard;
@BindView(R.id.streakCard)
StreakCard streakCard;
@BindView(R.id.subtitleCard)
SubtitleCard subtitleCard;
@BindView(R.id.overviewCard)
OverviewCard overviewCard;
@BindView(R.id.scoreCard)
ScoreCard scoreCard;
@BindView(R.id.historyCard)
HistoryCard historyCard;
@BindView(R.id.barCard)
BarCard barCard;
@BindView(R.id.toolbar)
Toolbar toolbar;
@NonNull
private Controller controller;
@Inject
public ShowHabitRootView(@NonNull @ActivityContext Context context,
@NonNull Habit habit)
{
super(context);
this.habit = habit;
addView(inflate(getContext(), R.layout.show_habit, null));
ButterKnife.bind(this);
controller = new Controller() {};
setDisplayHomeAsUp(true);
initCards();
initToolbar();
}
@Override
public int getToolbarColor()
{
StyledResources res = new StyledResources(getContext());
if (!res.getBoolean(R.attr.useHabitColorAsPrimary))
return super.getToolbarColor();
return PaletteUtils.getColor(getContext(), habit.getColor());
}
@Override
public void onModelChange()
{
new Handler(Looper.getMainLooper()).post(() -> {
toolbar.setTitle(habit.getName());
});
controller.onToolbarChanged();
}
public void setController(@NonNull Controller controller)
{
this.controller = controller;
historyCard.setController(controller);
}
@Override
protected void initToolbar()
{
super.initToolbar();
toolbar.setTitle(habit.getName());
}
@Override
protected void onAttachedToWindow()
{
super.onAttachedToWindow();
habit.getObservable().addListener(this);
}
@Override
protected void onDetachedFromWindow()
{
habit.getObservable().removeListener(this);
super.onDetachedFromWindow();
}
private void initCards()
{
subtitleCard.setHabit(habit);
overviewCard.setHabit(habit);
scoreCard.setHabit(habit);
historyCard.setHabit(habit);
streakCard.setHabit(habit);
frequencyCard.setHabit(habit);
barCard.setHabit(habit);
}
public interface Controller extends HistoryCard.Controller
{
default void onToolbarChanged() {}
}
}

View File

@@ -1,139 +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.activities.habits.show;
import android.support.annotation.*;
import org.isoron.androidbase.activities.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.common.dialogs.*;
import org.isoron.uhabits.activities.habits.edit.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.ui.callbacks.*;
import org.isoron.uhabits.core.ui.screens.habits.show.*;
import javax.inject.*;
import dagger.*;
@ActivityScope
public class ShowHabitScreen extends BaseScreen
implements ShowHabitMenuBehavior.Screen,
ShowHabitBehavior.Screen,
HistoryEditorDialog.Controller,
ShowHabitRootView.Controller
{
@NonNull
private final Habit habit;
@NonNull
private final EditHabitDialogFactory editHabitDialogFactory;
@NonNull
private final ConfirmDeleteDialogFactory confirmDeleteDialogFactory;
private final Lazy<ShowHabitBehavior> behavior;
@Inject
public ShowHabitScreen(@NonNull BaseActivity activity,
@NonNull Habit habit,
@NonNull ShowHabitRootView view,
@NonNull ShowHabitsMenu menu,
@NonNull EditHabitDialogFactory editHabitDialogFactory,
@NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory,
@NonNull Lazy<ShowHabitBehavior> behavior)
{
super(activity);
setMenu(menu);
setRootView(view);
this.habit = habit;
this.behavior = behavior;
this.editHabitDialogFactory = editHabitDialogFactory;
this.confirmDeleteDialogFactory = confirmDeleteDialogFactory;
view.setController(this);
}
@Override
public void onEditHistoryButtonClick()
{
behavior.get().onEditHistory();
}
@Override
public void onToggleCheckmark(Timestamp timestamp)
{
behavior.get().onToggleCheckmark(timestamp);
}
@Override
public void onToolbarChanged()
{
invalidateToolbar();
}
@Override
public void reattachDialogs()
{
super.reattachDialogs();
HistoryEditorDialog historyEditor = (HistoryEditorDialog) activity
.getSupportFragmentManager()
.findFragmentByTag("historyEditor");
if (historyEditor != null) historyEditor.setController(this);
}
@Override
public void showEditHabitScreen(@NonNull Habit habit)
{
activity.showDialog(editHabitDialogFactory.edit(habit), "editHabit");
}
@Override
public void showEditHistoryScreen()
{
HistoryEditorDialog dialog = new HistoryEditorDialog();
dialog.setHabit(habit);
dialog.setController(this);
dialog.show(activity.getSupportFragmentManager(), "historyEditor");
}
@Override
public void showMessage(ShowHabitMenuBehavior.Message m)
{
switch (m)
{
case COULD_NOT_EXPORT:
showMessage(R.string.could_not_export);
case HABIT_DELETED:
showMessage(R.string.delete_habits_message);
}
}
@Override
public void showDeleteConfirmationScreen(@NonNull OnConfirmedCallback callback) {
activity.showDialog(confirmDeleteDialogFactory.create(callback));
}
@Override
public void close() {
activity.finish();
}
}

View File

@@ -1,92 +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.activities.habits.show;
import android.support.annotation.*;
import android.view.*;
import org.isoron.androidbase.activities.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.preferences.Preferences;
import org.isoron.uhabits.core.ui.screens.habits.show.*;
import javax.inject.*;
import dagger.*;
@ActivityScope
public class ShowHabitsMenu extends BaseMenu
{
@NonNull
private Lazy<ShowHabitMenuBehavior> behavior;
@NonNull
private final Preferences prefs;
@Inject
public ShowHabitsMenu(@NonNull BaseActivity activity,
@NonNull Lazy<ShowHabitMenuBehavior> behavior,
@NonNull Preferences prefs)
{
super(activity);
this.behavior = behavior;
this.prefs = prefs;
}
@Override
public void onCreate(@NonNull Menu menu)
{
super.onCreate(menu);
if (prefs.isDeveloper())
menu.findItem(R.id.action_randomize).setVisible(true);
}
@Override
public boolean onItemSelected(@NonNull MenuItem item)
{
switch (item.getItemId())
{
case R.id.action_edit_habit:
behavior.get().onEditHabit();
return true;
case R.id.export:
behavior.get().onExportCSV();
return true;
case R.id.action_delete:
behavior.get().onDeleteHabit();
return true;
case R.id.action_randomize:
behavior.get().onRandomize();
return true;
default:
return false;
}
}
@Override
protected int getMenuResourceId()
{
return R.menu.show_habit;
}
}

View File

@@ -1,159 +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.activities.habits.show.views;
import android.content.*;
import android.support.annotation.*;
import android.util.*;
import android.widget.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.tasks.*;
import org.isoron.uhabits.utils.*;
import java.util.*;
import butterknife.*;
import static org.isoron.uhabits.activities.habits.show.views.ScoreCard.getTruncateField;
public class BarCard extends HabitCard
{
public static final int[] NUMERICAL_BUCKET_SIZES = {1, 7, 31, 92, 365};
public static final int[] BOOLEAN_BUCKET_SIZES = {7, 31, 92, 365};
@BindView(R.id.numericalSpinner)
Spinner numericalSpinner;
@BindView(R.id.boolSpinner)
Spinner boolSpinner;
@BindView(R.id.barChart)
BarChart chart;
@BindView(R.id.title)
TextView title;
@Nullable
private TaskRunner taskRunner;
private int bucketSize;
public BarCard(Context context)
{
super(context);
init();
}
public BarCard(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
@OnItemSelected(R.id.numericalSpinner)
public void onNumericalItemSelected(int position)
{
bucketSize = NUMERICAL_BUCKET_SIZES[position];
refreshData();
}
@OnItemSelected(R.id.boolSpinner)
public void onBoolItemSelected(int position)
{
bucketSize = BOOLEAN_BUCKET_SIZES[position];
refreshData();
}
@Override
protected void refreshData()
{
if (taskRunner == null) return;
taskRunner.execute(new RefreshTask(getHabit()));
}
private void init()
{
inflate(getContext(), R.layout.show_habit_bar, this);
ButterKnife.bind(this);
boolSpinner.setSelection(1);
numericalSpinner.setSelection(2);
bucketSize = 7;
Context appContext = getContext().getApplicationContext();
if (appContext instanceof HabitsApplication)
{
HabitsApplication app = (HabitsApplication) appContext;
taskRunner = app.getComponent().getTaskRunner();
}
if (isInEditMode()) initEditMode();
}
private void initEditMode()
{
int color = PaletteUtils.getAndroidTestColor(1);
title.setTextColor(color);
chart.setColor(color);
chart.populateWithRandomData();
}
private class RefreshTask implements Task
{
private final Habit habit;
public RefreshTask(Habit habit)
{
this.habit = habit;
}
@Override
public void doInBackground()
{
List<Checkmark> checkmarks;
if (bucketSize == 1) checkmarks = habit.getCheckmarks().getAll();
else checkmarks = habit.getCheckmarks().groupBy(getTruncateField(bucketSize));
chart.setCheckmarks(checkmarks);
chart.setBucketSize(bucketSize);
}
@Override
public void onPreExecute()
{
int color = PaletteUtils.getColor(getContext(), habit.getColor());
title.setTextColor(color);
chart.setColor(color);
if (habit.isNumerical())
{
boolSpinner.setVisibility(GONE);
chart.setTarget(habit.getTargetValue() * bucketSize);
}
else
{
numericalSpinner.setVisibility(GONE);
chart.setTarget(0);
}
}
}
}

View File

@@ -1,110 +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.activities.habits.show.views;
import android.content.*;
import android.support.annotation.*;
import android.util.*;
import android.widget.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.tasks.*;
import org.isoron.uhabits.utils.*;
import java.util.*;
import butterknife.*;
public class FrequencyCard extends HabitCard
{
@BindView(R.id.title)
TextView title;
@BindView(R.id.frequencyChart)
FrequencyChart chart;
@Nullable
private TaskRunner taskRunner;
public FrequencyCard(Context context)
{
super(context);
init();
}
public FrequencyCard(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
@Override
protected void refreshData()
{
if(taskRunner == null) return;
taskRunner.execute(new RefreshTask());
}
private void init()
{
inflate(getContext(), R.layout.show_habit_frequency, this);
ButterKnife.bind(this);
Context appContext = getContext().getApplicationContext();
if(appContext instanceof HabitsApplication)
{
HabitsApplication app = (HabitsApplication) appContext;
taskRunner = app.getComponent().getTaskRunner();
}
if (isInEditMode()) initEditMode();
}
private void initEditMode()
{
int color = PaletteUtils.getAndroidTestColor(1);
title.setTextColor(color);
chart.setColor(color);
chart.populateWithRandomData();
}
private class RefreshTask implements Task
{
@Override
public void doInBackground()
{
RepetitionList reps = getHabit().getRepetitions();
HashMap<Timestamp, Integer[]> frequency = reps.getWeekdayFrequency();
chart.setFrequency(frequency);
}
@Override
public void onPreExecute()
{
int paletteColor = getHabit().getColor();
int color = PaletteUtils.getColor(getContext(), paletteColor);
title.setTextColor(color);
chart.setColor(color);
}
}
}

View File

@@ -1,103 +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.activities.habits.show.views;
import android.content.*;
import android.support.annotation.*;
import android.util.*;
import android.widget.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.models.memory.*;
public abstract class HabitCard extends LinearLayout
implements ModelObservable.Listener
{
@NonNull
private Habit habit;
public HabitCard(Context context)
{
super(context);
init();
}
public HabitCard(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
@NonNull
public Habit getHabit()
{
return habit;
}
public void setHabit(@NonNull Habit habit)
{
detachFrom(this.habit);
attachTo(habit);
this.habit = habit;
}
@Override
public void onModelChange()
{
post(() -> refreshData());
}
@Override
protected void onAttachedToWindow()
{
if(isInEditMode()) return;
super.onAttachedToWindow();
refreshData();
attachTo(habit);
}
@Override
protected void onDetachedFromWindow()
{
detachFrom(habit);
super.onDetachedFromWindow();
}
protected abstract void refreshData();
private void attachTo(Habit habit)
{
habit.getObservable().addListener(this);
habit.getRepetitions().getObservable().addListener(this);
}
private void detachFrom(Habit habit)
{
habit.getRepetitions().getObservable().removeListener(this);
habit.getObservable().removeListener(this);
}
private void init()
{
if(!isInEditMode()) habit = new MemoryModelFactory().buildHabit();
}
}

View File

@@ -1,135 +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.activities.habits.show.views;
import android.content.*;
import android.support.annotation.*;
import android.util.*;
import android.widget.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.tasks.*;
import org.isoron.uhabits.utils.*;
import butterknife.*;
public class HistoryCard extends HabitCard
{
@BindView(R.id.historyChart)
HistoryChart chart;
@BindView(R.id.title)
TextView title;
@NonNull
private Controller controller;
@Nullable
private TaskRunner taskRunner;
public HistoryCard(Context context)
{
super(context);
init();
}
public HistoryCard(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
@OnClick(R.id.edit)
public void onClickEditButton()
{
controller.onEditHistoryButtonClick();
}
public void setController(@NonNull Controller controller)
{
this.controller = controller;
}
@Override
protected void refreshData()
{
if(taskRunner == null) return;
taskRunner.execute(new RefreshTask(getHabit()));
}
private void init()
{
inflate(getContext(), R.layout.show_habit_history, this);
ButterKnife.bind(this);
Context appContext = getContext().getApplicationContext();
if (appContext instanceof HabitsApplication)
{
HabitsApplication app = (HabitsApplication) appContext;
taskRunner = app.getComponent().getTaskRunner();
}
controller = new Controller() {};
if (isInEditMode()) initEditMode();
}
private void initEditMode()
{
int color = PaletteUtils.getAndroidTestColor(1);
title.setTextColor(color);
chart.setColor(color);
chart.populateWithRandomData();
}
public interface Controller
{
default void onEditHistoryButtonClick() {}
}
private class RefreshTask implements Task
{
private final Habit habit;
public RefreshTask(Habit habit) {this.habit = habit;}
@Override
public void doInBackground()
{
int checkmarks[] = habit.getCheckmarks().getAllValues();
chart.setCheckmarks(checkmarks);
}
@Override
public void onPreExecute()
{
int color = PaletteUtils.getColor(getContext(), habit.getColor());
title.setTextColor(color);
chart.setColor(color);
if(habit.isNumerical())
{
chart.setTarget((int) (habit.getTargetValue() * 1000));
chart.setNumerical(true);
}
}
}
}

View File

@@ -1,190 +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.activities.habits.show.views;
import android.content.*;
import android.support.annotation.*;
import android.util.*;
import android.widget.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.tasks.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.utils.*;
import butterknife.*;
public class OverviewCard extends HabitCard
{
@NonNull
private Cache cache;
@BindView(R.id.scoreRing)
RingView scoreRing;
@BindView(R.id.scoreLabel)
TextView scoreLabel;
@BindView(R.id.monthDiffLabel)
TextView monthDiffLabel;
@BindView(R.id.yearDiffLabel)
TextView yearDiffLabel;
@BindView(R.id.totalCountLabel)
TextView totalCountLabel;
@BindView(R.id.title)
TextView title;
private int color;
@Nullable
private TaskRunner taskRunner;
public OverviewCard(Context context)
{
super(context);
init();
}
public OverviewCard(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
@Override
protected void refreshData()
{
if(taskRunner == null) return;
taskRunner.execute(new RefreshTask());
}
private String formatPercentageDiff(float percentageDiff)
{
return String.format("%s%.0f%%", (percentageDiff >= 0 ? "+" : "\u2212"),
Math.abs(percentageDiff) * 100);
}
private void init()
{
Context appContext = getContext().getApplicationContext();
if (appContext instanceof HabitsApplication)
{
HabitsApplication app = (HabitsApplication) appContext;
taskRunner = app.getComponent().getTaskRunner();
}
inflate(getContext(), R.layout.show_habit_overview, this);
ButterKnife.bind(this);
cache = new Cache();
if (isInEditMode()) initEditMode();
}
private void initEditMode()
{
color = PaletteUtils.getAndroidTestColor(1);
cache.todayScore = 0.6f;
cache.lastMonthScore = 0.42f;
cache.lastYearScore = 0.75f;
refreshColors();
refreshScore();
}
private void refreshColors()
{
scoreRing.setColor(color);
scoreLabel.setTextColor(color);
title.setTextColor(color);
}
private void refreshScore()
{
float todayPercentage = cache.todayScore;
float monthDiff = todayPercentage - cache.lastMonthScore;
float yearDiff = todayPercentage - cache.lastYearScore;
scoreRing.setPercentage(todayPercentage);
scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100));
monthDiffLabel.setText(formatPercentageDiff(monthDiff));
yearDiffLabel.setText(formatPercentageDiff(yearDiff));
totalCountLabel.setText(String.valueOf(cache.totalCount));
StyledResources res = new StyledResources(getContext());
int inactiveColor = res.getColor(R.attr.mediumContrastTextColor);
monthDiffLabel.setTextColor(monthDiff >= 0 ? color : inactiveColor);
yearDiffLabel.setTextColor(yearDiff >= 0 ? color : inactiveColor);
totalCountLabel.setTextColor(yearDiff >= 0 ? color : inactiveColor);
postInvalidate();
}
private class Cache
{
public float todayScore;
public float lastMonthScore;
public float lastYearScore;
public long totalCount;
}
private class RefreshTask implements Task
{
@Override
public void doInBackground()
{
Habit habit = getHabit();
ScoreList scores = habit.getScores();
Timestamp today = DateUtils.getToday();
Timestamp lastMonth = today.minus(30);
Timestamp lastYear = today.minus(365);
cache.todayScore = (float) scores.getTodayValue();
cache.lastMonthScore = (float) scores.getValue(lastMonth);
cache.lastYearScore = (float) scores.getValue(lastYear);
cache.totalCount = habit.getRepetitions().getTotalCount();
}
@Override
public void onPostExecute()
{
refreshScore();
}
@Override
public void onPreExecute()
{
color = PaletteUtils.getColor(getContext(), getHabit().getColor());
refreshColors();
}
}
}

View File

@@ -1,167 +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.activities.habits.show.views;
import android.content.*;
import android.support.annotation.*;
import android.util.*;
import android.widget.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.tasks.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.utils.*;
import java.util.*;
import butterknife.*;
public class ScoreCard extends HabitCard
{
public static final int[] BUCKET_SIZES = { 1, 7, 31, 92, 365 };
@BindView(R.id.spinner)
Spinner spinner;
@BindView(R.id.scoreView)
ScoreChart chart;
@BindView(R.id.title)
TextView title;
private int bucketSize;
@Nullable
private TaskRunner taskRunner;
@Nullable
private Preferences prefs;
public ScoreCard(Context context)
{
super(context);
init();
}
public ScoreCard(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
@NonNull
public static DateUtils.TruncateField getTruncateField(int bucketSize)
{
if (bucketSize == 7) return DateUtils.TruncateField.WEEK_NUMBER;
if (bucketSize == 31) return DateUtils.TruncateField.MONTH;
if (bucketSize == 92) return DateUtils.TruncateField.QUARTER;
if (bucketSize == 365) return DateUtils.TruncateField.YEAR;
Log.e("ScoreCard",
String.format("Unknown bucket size: %d", bucketSize));
return DateUtils.TruncateField.MONTH;
}
@OnItemSelected(R.id.spinner)
public void onItemSelected(int position)
{
setBucketSizeFromPosition(position);
HabitsApplication app =
(HabitsApplication) getContext().getApplicationContext();
app.getComponent().getWidgetUpdater().updateWidgets();
refreshData();
}
@Override
protected void refreshData()
{
if(taskRunner == null) return;
taskRunner.execute(new RefreshTask());
}
private int getDefaultSpinnerPosition()
{
if(prefs == null) return 0;
return prefs.getDefaultScoreSpinnerPosition();
}
private void init()
{
Context appContext = getContext().getApplicationContext();
if (appContext instanceof HabitsApplication)
{
HabitsApplication app = (HabitsApplication) appContext;
taskRunner = app.getComponent().getTaskRunner();
prefs = app.getComponent().getPreferences();
}
inflate(getContext(), R.layout.show_habit_score, this);
ButterKnife.bind(this);
int defaultPosition = getDefaultSpinnerPosition();
setBucketSizeFromPosition(defaultPosition);
spinner.setSelection(defaultPosition);
if (isInEditMode())
{
spinner.setVisibility(GONE);
title.setTextColor(PaletteUtils.getAndroidTestColor(1));
chart.setColor(PaletteUtils.getAndroidTestColor(1));
chart.populateWithRandomData();
}
}
private void setBucketSizeFromPosition(int position)
{
if(prefs == null) return;
prefs.setDefaultScoreSpinnerPosition(position);
bucketSize = BUCKET_SIZES[position];
}
private class RefreshTask implements Task
{
@Override
public void doInBackground()
{
List<Score> scores;
ScoreList scoreList = getHabit().getScores();
if (bucketSize == 1) scores = scoreList.toList();
else scores = scoreList.groupBy(getTruncateField(bucketSize));
chart.setScores(scores);
chart.setBucketSize(bucketSize);
}
@Override
public void onPreExecute()
{
int color =
PaletteUtils.getColor(getContext(), getHabit().getColor());
title.setTextColor(color);
chart.setColor(color);
}
}
}

View File

@@ -1,119 +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.activities.habits.show.views;
import android.content.*;
import android.support.annotation.*;
import android.util.*;
import android.widget.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.tasks.*;
import org.isoron.uhabits.utils.*;
import java.util.*;
import butterknife.*;
public class StreakCard extends HabitCard
{
public static final int NUM_STREAKS = 10;
@BindView(R.id.title)
TextView title;
@BindView(R.id.streakChart)
StreakChart streakChart;
@Nullable
private TaskRunner taskRunner;
public StreakCard(Context context)
{
super(context);
init();
}
public StreakCard(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
@Override
protected void refreshData()
{
if(taskRunner == null) return;
taskRunner.execute(new RefreshTask());
}
private void init()
{
Context appContext = getContext().getApplicationContext();
if (appContext instanceof HabitsApplication)
{
HabitsApplication app = (HabitsApplication) appContext;
taskRunner = app.getComponent().getTaskRunner();
}
inflate(getContext(), R.layout.show_habit_streak, this);
ButterKnife.bind(this);
setOrientation(VERTICAL);
if (isInEditMode()) initEditMode();
}
private void initEditMode()
{
int color = PaletteUtils.getAndroidTestColor(1);
title.setTextColor(color);
streakChart.setColor(color);
streakChart.populateWithRandomData();
}
private class RefreshTask implements Task
{
public List<Streak> bestStreaks;
@Override
public void doInBackground()
{
StreakList streaks = getHabit().getStreaks();
bestStreaks = streaks.getBest(NUM_STREAKS);
}
@Override
public void onPostExecute()
{
streakChart.setStreaks(bestStreaks);
}
@Override
public void onPreExecute()
{
int color =
PaletteUtils.getColor(getContext(), getHabit().getColor());
title.setTextColor(color);
streakChart.setColor(color);
}
}
}

View File

@@ -1,114 +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.activities.habits.show.views;
import android.annotation.*;
import android.content.*;
import android.content.res.*;
import android.util.*;
import android.widget.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.utils.*;
import butterknife.*;
public class SubtitleCard extends HabitCard
{
@BindView(R.id.questionLabel)
TextView questionLabel;
@BindView(R.id.frequencyLabel)
TextView frequencyLabel;
@BindView(R.id.reminderLabel)
TextView reminderLabel;
public SubtitleCard(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
@Override
protected void refreshData()
{
Habit habit = getHabit();
int color = PaletteUtils.getColor(getContext(), habit.getColor());
reminderLabel.setText(getResources().getString(R.string.reminder_off));
questionLabel.setVisibility(VISIBLE);
questionLabel.setTextColor(color);
questionLabel.setText(habit.getDescription());
frequencyLabel.setText(toText(habit.getFrequency()));
if (habit.hasReminder()) updateReminderText(habit.getReminder());
if (habit.getDescription().isEmpty()) questionLabel.setVisibility(GONE);
invalidate();
}
private void init()
{
inflate(getContext(), R.layout.show_habit_subtitle, this);
ButterKnife.bind(this);
if (isInEditMode()) initEditMode();
}
@SuppressLint("SetTextI18n")
private void initEditMode()
{
questionLabel.setTextColor(PaletteUtils.getAndroidTestColor(1));
questionLabel.setText("Have you meditated today?");
reminderLabel.setText("08:00");
}
private String toText(Frequency freq)
{
Resources resources = getResources();
Integer num = freq.getNumerator();
Integer den = freq.getDenominator();
if (num.equals(den)) return resources.getString(R.string.every_day);
if (num == 1)
{
if (den == 7) return resources.getString(R.string.every_week);
if (den % 7 == 0)
return resources.getString(R.string.every_x_weeks, den / 7);
return resources.getString(R.string.every_x_days, den);
}
String times_every = resources.getString(R.string.times_every);
return String.format("%d %s %d %s", num, times_every, den,
resources.getString(R.string.days));
}
private void updateReminderText(Reminder reminder)
{
reminderLabel.setText(
AndroidDateUtils.formatTime(getContext(), reminder.getHour(),
reminder.getMinute()));
}
}

View File

@@ -1,68 +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.activities.intro;
import android.graphics.*;
import android.os.*;
import com.github.paolorotolo.appintro.*;
import org.isoron.uhabits.R;
/**
* Activity that introduces the app to the user, shown only after the app is
* launched for the first time.
*/
public class IntroActivity extends AppIntro2
{
@Override
public void init(Bundle savedInstanceState)
{
showStatusBar(false);
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_1),
getString(R.string.intro_description_1), R.drawable.intro_icon_1,
Color.parseColor("#194673")));
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_2),
getString(R.string.intro_description_2), R.drawable.intro_icon_2,
Color.parseColor("#ffa726")));
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_4),
getString(R.string.intro_description_4), R.drawable.intro_icon_4,
Color.parseColor("#9575cd")));
}
@Override
public void onNextPressed()
{
}
@Override
public void onDonePressed()
{
finish();
}
@Override
public void onSlideChanged()
{
}
}

View File

@@ -1,51 +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.activities.settings;
import android.os.*;
import org.isoron.androidbase.activities.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.R;
/**
* Activity that allows the user to view and modify the app settings.
*/
public class SettingsActivity extends BaseActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_activity);
setupActionBarColor();
}
private void setupActionBarColor()
{
StyledResources res = new StyledResources(this);
int color = BaseScreen.getDefaultActionBarColor(this);
if (res.getBoolean(R.attr.useHabitColorAsPrimary))
color = res.getColor(R.attr.aboutScreenColor);
BaseScreen.setupActionBarColor(this, color);
}
}

View File

@@ -1,211 +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.activities.settings;
import android.app.backup.*;
import android.content.*;
import android.net.*;
import android.os.*;
import android.provider.*;
import android.support.annotation.*;
import android.support.v7.preference.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.ui.*;
import org.isoron.uhabits.notifications.*;
import static android.media.RingtoneManager.*;
import static android.os.Build.VERSION.SDK_INT;
import static org.isoron.uhabits.activities.habits.list.ListHabitsScreenKt.*;
public class SettingsFragment extends PreferenceFragmentCompat
implements SharedPreferences.OnSharedPreferenceChangeListener
{
private static int RINGTONE_REQUEST_CODE = 1;
private SharedPreferences sharedPrefs;
private RingtoneManager ringtoneManager;
@Nullable
private Preferences prefs;
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == RINGTONE_REQUEST_CODE)
{
ringtoneManager.update(data);
updateRingtoneDescription();
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
Context appContext = getContext().getApplicationContext();
if (appContext instanceof HabitsApplication)
{
HabitsApplication app = (HabitsApplication) appContext;
prefs = app.getComponent().getPreferences();
}
setResultOnPreferenceClick("importData", RESULT_IMPORT_DATA);
setResultOnPreferenceClick("exportCSV", RESULT_EXPORT_CSV);
setResultOnPreferenceClick("exportDB", RESULT_EXPORT_DB);
setResultOnPreferenceClick("repairDB", RESULT_REPAIR_DB);
setResultOnPreferenceClick("bugReport", RESULT_BUG_REPORT);
}
@Override
public void onCreatePreferences(Bundle bundle, String s)
{
// NOP
}
@Override
public void onPause()
{
sharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
super.onPause();
}
@Override
public boolean onPreferenceTreeClick(Preference preference)
{
String key = preference.getKey();
if (key == null) return false;
if (key.equals("reminderSound"))
{
showRingtonePicker();
return true;
}
else if (key.equals("reminderCustomize"))
{
if (SDK_INT < Build.VERSION_CODES.O) return true;
AndroidNotificationTray.Companion.createAndroidNotificationChannel(getContext());
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getContext().getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationTray.REMINDERS_CHANNEL_ID);
startActivity(intent);
return true;
}
return super.onPreferenceTreeClick(preference);
}
@Override
public void onResume()
{
super.onResume();
this.ringtoneManager = new RingtoneManager(getActivity());
sharedPrefs = getPreferenceManager().getSharedPreferences();
sharedPrefs.registerOnSharedPreferenceChangeListener(this);
if (prefs != null && !prefs.isDeveloper())
{
PreferenceCategory devCategory =
(PreferenceCategory) findPreference("devCategory");
devCategory.removeAll();
devCategory.setVisible(false);
}
if (SDK_INT < Build.VERSION_CODES.O)
findPreference("reminderCustomize").setVisible(false);
else
{
findPreference("reminderSound").setVisible(false);
findPreference("pref_snooze_interval").setVisible(false);
}
updateSync();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key)
{
BackupManager.dataChanged("org.isoron.uhabits");
updateSync();
}
private void setResultOnPreferenceClick(String key, final int result)
{
Preference pref = findPreference(key);
pref.setOnPreferenceClickListener(preference ->
{
getActivity().setResult(result);
getActivity().finish();
return true;
});
}
private void showRingtonePicker()
{
Uri existingRingtoneUri = ringtoneManager.getURI();
Uri defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI;
Intent intent = new Intent(ACTION_RINGTONE_PICKER);
intent.putExtra(EXTRA_RINGTONE_TYPE, TYPE_NOTIFICATION);
intent.putExtra(EXTRA_RINGTONE_SHOW_DEFAULT, true);
intent.putExtra(EXTRA_RINGTONE_SHOW_SILENT, true);
intent.putExtra(EXTRA_RINGTONE_DEFAULT_URI, defaultRingtoneUri);
intent.putExtra(EXTRA_RINGTONE_EXISTING_URI, existingRingtoneUri);
startActivityForResult(intent, RINGTONE_REQUEST_CODE);
}
private void updateRingtoneDescription()
{
String ringtoneName = ringtoneManager.getName();
if (ringtoneName == null) return;
Preference ringtonePreference = findPreference("reminderSound");
ringtonePreference.setSummary(ringtoneName);
}
private void updateSync()
{
if (prefs == null) return;
boolean enabled = prefs.isSyncEnabled();
Preference syncKey = findPreference("pref_sync_key");
if (syncKey != null)
{
syncKey.setSummary(prefs.getSyncKey());
syncKey.setVisible(enabled);
}
Preference syncAddress = findPreference("pref_sync_address");
if (syncAddress != null)
{
syncAddress.setSummary(prefs.getSyncAddress());
syncAddress.setVisible(enabled);
}
}
}

View File

@@ -1,44 +0,0 @@
/*
* Copyright (C) 2015-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.automation
import android.os.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
class EditSettingActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val app = applicationContext as HabitsApplication
val habits = app.component.habitList.getFiltered(
HabitMatcherBuilder()
.setArchivedAllowed(false)
.setCompletedAllowed(true)
.build())
val controller = EditSettingController(this)
val rootView = EditSettingRootView(this, habits, controller)
val screen = BaseScreen(this)
screen.setRootView(rootView)
setScreen(screen)
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright (C) 2015-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.automation
import android.app.*
import android.content.*
import android.os.*
import org.isoron.uhabits.*
import org.isoron.uhabits.automation.FireSettingReceiver.*
import org.isoron.uhabits.core.models.*
class EditSettingController(private val activity: Activity) {
fun onSave(habit: Habit, action: Int) {
if (habit.getId() == null) return
val actionName = getActionName(action)
val blurb = String.format("%s: %s", actionName, habit.name)
val bundle = Bundle()
bundle.putInt("action", action)
bundle.putLong("habit", habit.getId()!!)
activity.setResult(Activity.RESULT_OK, Intent().apply {
putExtra(EXTRA_STRING_BLURB, blurb)
putExtra(EXTRA_BUNDLE, bundle)
})
activity.finish()
}
private fun getActionName(action: Int): String {
when (action) {
ACTION_CHECK -> return activity.getString(R.string.check)
ACTION_UNCHECK -> return activity.getString(R.string.uncheck)
ACTION_TOGGLE -> return activity.getString(R.string.toggle)
else -> return "???"
}
}
}

View File

@@ -1,81 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.automation
import android.R.layout.*
import android.content.*
import android.support.v7.widget.*
import android.support.v7.widget.Toolbar
import android.widget.*
import butterknife.*
import org.isoron.androidbase.activities.*
import org.isoron.androidbase.utils.*
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.*
import java.util.*
class EditSettingRootView(
context: Context,
private val habitList: HabitList,
private val controller: EditSettingController
) : BaseRootView(context) {
@BindView(R.id.toolbar)
lateinit var tbar: Toolbar
@BindView(R.id.habitSpinner)
lateinit var habitSpinner: AppCompatSpinner
@BindView(R.id.actionSpinner)
lateinit var actionSpinner: AppCompatSpinner
init {
addView(inflate(getContext(), R.layout.automation, null))
ButterKnife.bind(this)
populateHabitSpinner()
}
override fun getToolbar(): Toolbar {
return tbar
}
override fun getToolbarColor(): Int {
val res = StyledResources(context)
if (!res.getBoolean(R.attr.useHabitColorAsPrimary))
return super.getToolbarColor()
return res.getColor(R.attr.aboutScreenColor)
}
@OnClick(R.id.buttonSave)
fun onClickSave() {
val action = actionSpinner.selectedItemPosition
val habitPosition = habitSpinner.selectedItemPosition
val habit = habitList.getByPosition(habitPosition)
controller.onSave(habit, action)
}
private fun populateHabitSpinner() {
val names = habitList.mapTo(LinkedList<String>()) { it.name }
val adapter = ArrayAdapter(context, simple_spinner_item, names)
adapter.setDropDownViewResource(simple_spinner_dropdown_item)
habitSpinner.adapter = adapter
}
}

View File

@@ -1,73 +0,0 @@
/*
* Copyright (C) 2015-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.automation
import android.content.*
import dagger.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.ui.widgets.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.receivers.*
const val ACTION_CHECK = 0
const val ACTION_UNCHECK = 1
const val ACTION_TOGGLE = 2
const val EXTRA_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE"
const val EXTRA_STRING_BLURB = "com.twofortyfouram.locale.intent.extra.BLURB"
class FireSettingReceiver : BroadcastReceiver() {
private lateinit var allHabits: HabitList
override fun onReceive(context: Context, intent: Intent) {
val app = context.applicationContext as HabitsApplication
val component = DaggerFireSettingReceiver_ReceiverComponent
.builder()
.habitsApplicationComponent(app.component)
.build()
allHabits = app.component.habitList
val args = parseIntent(intent) ?: return
val timestamp = DateUtils.getToday()
val controller = component.widgetController
when (args.action) {
ACTION_CHECK -> controller.onAddRepetition(args.habit, timestamp)
ACTION_UNCHECK -> controller.onRemoveRepetition(args.habit, timestamp)
ACTION_TOGGLE -> controller.onToggleRepetition(args.habit, timestamp)
}
}
private fun parseIntent(intent: Intent): Arguments? {
val bundle = intent.getBundleExtra(EXTRA_BUNDLE) ?: return null
val action = bundle.getInt("action")
if (action < 0 || action > 2) return null
val habit = allHabits.getById(bundle.getLong("habit")) ?: return null
return Arguments(action, habit)
}
@ReceiverScope
@Component(dependencies = arrayOf(HabitsApplicationComponent::class))
internal interface ReceiverComponent {
val widgetController: WidgetBehavior
}
private class Arguments(var action: Int, var habit: Habit)
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright (C) 2015-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.database
import org.isoron.uhabits.core.database.*
class AndroidCursor(private val cursor: android.database.Cursor) : Cursor {
override fun close() = cursor.close()
override fun moveToNext() = cursor.moveToNext()
override fun getInt(index: Int): Int? {
if (cursor.isNull(index)) return null
else return cursor.getInt(index)
}
override fun getLong(index: Int): Long? {
if (cursor.isNull(index)) return null
else return cursor.getLong(index)
}
override fun getDouble(index: Int): Double? {
if (cursor.isNull(index)) return null
else return cursor.getDouble(index)
}
override fun getString(index: Int): String? {
if (cursor.isNull(index)) return null
else return cursor.getString(index)
}
}

View File

@@ -1,74 +0,0 @@
/*
* Copyright (C) 2015-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.database
import android.content.*
import android.database.sqlite.*
import org.isoron.uhabits.core.database.*
class AndroidDatabase(private val db: SQLiteDatabase) : Database {
override fun beginTransaction() = db.beginTransaction()
override fun setTransactionSuccessful() = db.setTransactionSuccessful()
override fun endTransaction() = db.endTransaction()
override fun close() = db.close()
override fun getVersion() = db.version
override fun query(query: String, vararg params: String)
= AndroidCursor(db.rawQuery(query, params))
override fun execute(query: String, vararg params: Any)
= db.execSQL(query, params)
override fun update(tableName: String,
map: Map<String, Any?>,
where: String,
vararg params: String): Int {
val values = mapToContentValues(map)
return db.update(tableName, values, where, params)
}
override fun insert(tableName: String, map: Map<String, Any?>): Long? {
val values = mapToContentValues(map)
return db.insert(tableName, null, values)
}
override fun delete(tableName: String,
where: String,
vararg params: String) {
db.delete(tableName, where, params)
}
private fun mapToContentValues(map: Map<String, Any?>): ContentValues {
val values = ContentValues()
for ((key, value) in map) {
when (value) {
null -> values.putNull(key)
is Int -> values.put(key, value)
is Long -> values.put(key, value)
is Double -> values.put(key, value)
is String -> values.put(key, value)
else -> throw IllegalStateException(
"unsupported type: " + value)
}
}
return values
}
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright (C) 2015-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.database
import android.database.sqlite.*
import org.isoron.uhabits.core.database.*
import java.io.*
import javax.inject.*
class AndroidDatabaseOpener @Inject constructor() : DatabaseOpener {
override fun open(file: File): AndroidDatabase {
return AndroidDatabase(SQLiteDatabase.openDatabase(
file.absolutePath, null, SQLiteDatabase.OPEN_READWRITE))
}
}

View File

@@ -1,78 +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.intents
import android.content.*
import android.net.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.about.*
import org.isoron.uhabits.activities.habits.show.*
import org.isoron.uhabits.activities.intro.*
import org.isoron.uhabits.activities.settings.*
import org.isoron.uhabits.core.models.*
import javax.inject.*
class IntentFactory
@Inject constructor() {
fun helpTranslate(context: Context) =
buildViewIntent(context.getString(R.string.translateURL))
fun openDocument() = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
}
fun rateApp(context: Context) =
buildViewIntent(context.getString(R.string.playStoreURL))
fun sendFeedback(context: Context) =
buildSendToIntent(context.getString(R.string.feedbackURL))
fun startAboutActivity(context: Context) =
Intent(context, AboutActivity::class.java)
fun startIntroActivity(context: Context) =
Intent(context, IntroActivity::class.java)
fun startSettingsActivity(context: Context) =
Intent(context, SettingsActivity::class.java)
fun startShowHabitActivity(context: Context, habit: Habit) =
Intent(context, ShowHabitActivity::class.java).apply {
data = Uri.parse(habit.uriString)
}
fun viewFAQ(context: Context) =
buildViewIntent(context.getString(R.string.helpURL))
fun viewSourceCode(context: Context) =
buildViewIntent(context.getString(R.string.sourceCodeURL))
private fun buildSendToIntent(url: String) = Intent().apply {
action = Intent.ACTION_SENDTO
data = Uri.parse(url)
}
private fun buildViewIntent(url: String) = Intent().apply {
action = Intent.ACTION_VIEW
data = Uri.parse(url)
}
}

View File

@@ -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.intents
import android.content.*
import android.content.ContentUris.*
import android.net.*
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.utils.*
import javax.inject.*
@AppScope
class IntentParser
@Inject constructor(private val habits: HabitList) {
fun parseCheckmarkIntent(intent: Intent): CheckmarkIntentData {
val uri = intent.data ?: throw IllegalArgumentException("uri is null")
return CheckmarkIntentData(parseHabit(uri), parseTimestamp(intent))
}
private fun parseHabit(uri: Uri): Habit {
val habit = habits.getById(parseId(uri)) ?:
throw IllegalArgumentException("habit not found")
return habit
}
private fun parseTimestamp(intent: Intent): Timestamp {
val today = DateUtils.getToday().unixTime
var timestamp = intent.getLongExtra("timestamp", today)
timestamp = DateUtils.getStartOfDay(timestamp)
if (timestamp < 0 || timestamp > today)
throw IllegalArgumentException("timestamp is not valid")
return Timestamp(timestamp)
}
class CheckmarkIntentData(var habit: Habit, var timestamp: Timestamp)
}

View File

@@ -1,60 +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.intents
import android.app.*
import android.app.AlarmManager.*
import android.content.*
import android.content.Context.*
import android.os.Build.VERSION.*
import android.os.Build.VERSION_CODES.*
import org.isoron.androidbase.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.reminders.*
import javax.inject.*
@AppScope
class IntentScheduler
@Inject constructor(
@AppContext context: Context,
private val pendingIntents: PendingIntentFactory,
private val logger: HabitLogger
) : ReminderScheduler.SystemScheduler {
private val manager =
context.getSystemService(ALARM_SERVICE) as AlarmManager
fun schedule(timestamp: Long, intent: PendingIntent) {
if (SDK_INT >= M)
manager.setExactAndAllowWhileIdle(RTC_WAKEUP, timestamp, intent)
else
manager.setExact(RTC_WAKEUP, timestamp, intent)
}
override fun scheduleShowReminder(reminderTime: Long,
habit: Habit,
timestamp: Long) {
val intent = pendingIntents.showReminder(habit, reminderTime, timestamp)
schedule(reminderTime, intent)
logger.logReminderScheduled(habit, reminderTime)
}
}

View File

@@ -1,106 +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.intents
import android.app.*
import android.app.PendingIntent.*
import android.content.*
import android.net.*
import org.isoron.androidbase.*
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.receivers.*
import javax.inject.*
@AppScope
class PendingIntentFactory
@Inject constructor(
@AppContext private val context: Context,
private val intentFactory: IntentFactory) {
fun addCheckmark(habit: Habit, timestamp: Timestamp?): PendingIntent =
PendingIntent.getBroadcast(
context, 1,
Intent(context, WidgetReceiver::class.java).apply {
data = Uri.parse(habit.uriString)
action = WidgetReceiver.ACTION_ADD_REPETITION
if (timestamp != null) putExtra("timestamp", timestamp.unixTime)
},
FLAG_UPDATE_CURRENT)
fun dismissNotification(habit: Habit): PendingIntent =
PendingIntent.getBroadcast(
context, 0,
Intent(context, ReminderReceiver::class.java).apply {
action = WidgetReceiver.ACTION_DISMISS_REMINDER
data = Uri.parse(habit.uriString)
},
FLAG_UPDATE_CURRENT)
fun removeRepetition(habit: Habit): PendingIntent =
PendingIntent.getBroadcast(
context, 3,
Intent(context, WidgetReceiver::class.java).apply {
action = WidgetReceiver.ACTION_REMOVE_REPETITION
data = Uri.parse(habit.uriString)
},
FLAG_UPDATE_CURRENT)
fun showHabit(habit: Habit): PendingIntent =
android.support.v4.app.TaskStackBuilder
.create(context)
.addNextIntentWithParentStack(
intentFactory.startShowHabitActivity(
context, habit))
.getPendingIntent(0, FLAG_UPDATE_CURRENT)!!
fun showReminder(habit: Habit,
reminderTime: Long?,
timestamp: Long): PendingIntent =
PendingIntent.getBroadcast(
context,
(habit.getId()!! % Integer.MAX_VALUE).toInt() + 1,
Intent(context, ReminderReceiver::class.java).apply {
action = ReminderReceiver.ACTION_SHOW_REMINDER
data = Uri.parse(habit.uriString)
putExtra("timestamp", timestamp)
putExtra("reminderTime", reminderTime)
},
FLAG_UPDATE_CURRENT)
fun snoozeNotification(habit: Habit): PendingIntent =
PendingIntent.getBroadcast(
context, 0,
Intent(context, ReminderReceiver::class.java).apply {
data = Uri.parse(habit.uriString)
action = ReminderReceiver.ACTION_SNOOZE_REMINDER
},
FLAG_UPDATE_CURRENT)
fun toggleCheckmark(habit: Habit, timestamp: Long?): PendingIntent =
PendingIntent.getBroadcast(
context, 2,
Intent(context, WidgetReceiver::class.java).apply {
data = Uri.parse(habit.uriString)
action = WidgetReceiver.ACTION_TOGGLE_REPETITION
if (timestamp != null) putExtra("timestamp", timestamp)
},
FLAG_UPDATE_CURRENT)
}

View File

@@ -1,161 +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.notifications
import android.app.*
import android.content.*
import android.graphics.*
import android.graphics.BitmapFactory.*
import android.os.*
import android.os.Build.VERSION.*
import android.support.annotation.*
import android.support.v4.app.*
import android.support.v4.app.NotificationCompat.*
import org.isoron.androidbase.*
import org.isoron.uhabits.R
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.intents.*
import javax.inject.*
@AppScope
class AndroidNotificationTray
@Inject constructor(
@AppContext private val context: Context,
private val pendingIntents: PendingIntentFactory,
private val preferences: Preferences,
private val ringtoneManager: RingtoneManager
) : NotificationTray.SystemTray {
private var active = HashSet<Int>()
override fun removeNotification(id: Int) {
val manager = NotificationManagerCompat.from(context)
manager.cancel(id)
active.remove(id)
// Clear the group summary notification
if(active.isEmpty()) manager.cancelAll()
}
override fun showNotification(habit: Habit,
notificationId: Int,
timestamp: Timestamp,
reminderTime: Long)
{
val notificationManager = NotificationManagerCompat.from(context)
val summary = buildSummary(reminderTime)
notificationManager.notify(Int.MAX_VALUE, summary)
val notification = buildNotification(habit, reminderTime, timestamp)
createAndroidNotificationChannel(context)
notificationManager.notify(notificationId, notification)
active.add(notificationId)
}
@NonNull
fun buildNotification(@NonNull habit: Habit,
@NonNull reminderTime: Long,
@NonNull timestamp: Timestamp) : Notification
{
val addRepetitionAction = Action(
R.drawable.ic_action_check,
context.getString(R.string.yes),
pendingIntents.addCheckmark(habit, timestamp))
val removeRepetitionAction = Action(
R.drawable.ic_action_cancel,
context.getString(R.string.no),
pendingIntents.removeRepetition(habit))
val wearableBg = decodeResource(context.resources, R.drawable.stripe)
// Even though the set of actions is the same on the phone and
// on the watch, Pebble requires us to add them to the
// WearableExtender.
val wearableExtender = WearableExtender()
.setBackground(wearableBg)
.addAction(addRepetitionAction)
.addAction(removeRepetitionAction)
val builder = NotificationCompat.Builder(context, REMINDERS_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(habit.name)
.setContentText(habit.description)
.setContentIntent(pendingIntents.showHabit(habit))
.setDeleteIntent(pendingIntents.dismissNotification(habit))
.addAction(addRepetitionAction)
.addAction(removeRepetitionAction)
.setSound(ringtoneManager.getURI())
.setWhen(reminderTime)
.setShowWhen(true)
.setOngoing(preferences.shouldMakeNotificationsSticky())
.setGroup("default")
if (preferences.shouldMakeNotificationsLed())
builder.setLights(Color.RED, 1000, 1000)
if(SDK_INT < Build.VERSION_CODES.O) {
val snoozeAction = Action(R.drawable.ic_action_snooze,
context.getString(R.string.snooze),
pendingIntents.snoozeNotification(habit))
wearableExtender.addAction(snoozeAction)
builder.addAction(snoozeAction)
}
builder.extend(wearableExtender)
return builder.build()
}
@NonNull
private fun buildSummary(@NonNull reminderTime: Long) : Notification
{
return NotificationCompat.Builder(context, REMINDERS_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(context.getString(R.string.app_name))
.setWhen(reminderTime)
.setShowWhen(true)
.setGroup("default")
.setGroupSummary(true)
.build()
}
companion object {
private val REMINDERS_CHANNEL_ID = "REMINDERS"
fun createAndroidNotificationChannel(context: Context) {
val notificationManager = context.getSystemService(Activity.NOTIFICATION_SERVICE)
as NotificationManager
if (SDK_INT >= Build.VERSION_CODES.O)
{
val channel = NotificationChannel(REMINDERS_CHANNEL_ID,
context.resources.getString(R.string.reminder),
NotificationManager.IMPORTANCE_DEFAULT)
notificationManager.createNotificationChannel(channel)
}
}
}
}

View File

@@ -1,74 +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.notifications
import android.content.*
import android.media.RingtoneManager.*
import android.net.*
import android.preference.*
import android.provider.*
import org.isoron.androidbase.*
import org.isoron.uhabits.R
import org.isoron.uhabits.core.*
import javax.inject.*
@AppScope
class RingtoneManager
@Inject constructor(@AppContext private val context: Context) {
val prefs: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context)
fun getName(): String? {
try {
var ringtoneName = context.resources.getString(R.string.none)
val ringtoneUri = getURI()
if (ringtoneUri != null) {
val ringtone = getRingtone(context, ringtoneUri)
if (ringtone != null) ringtoneName = ringtone.getTitle(context)
}
return ringtoneName
} catch (e: RuntimeException) {
e.printStackTrace()
return null
}
}
fun getURI(): Uri? {
var ringtoneUri: Uri? = null
val defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI
val prefRingtoneUri = prefs.getString("pref_ringtone_uri",
defaultRingtoneUri.toString())
if (prefRingtoneUri.isNotEmpty())
ringtoneUri = Uri.parse(prefRingtoneUri)
return ringtoneUri
}
fun update(data: Intent?) {
if (data == null) return
val ringtoneUri = data.getParcelableExtra<Uri>(EXTRA_RINGTONE_PICKED_URI)
if (ringtoneUri != null) {
prefs.edit().putString("pref_ringtone_uri", ringtoneUri.toString()).apply()
} else {
prefs.edit().putString("pref_ringtone_uri", "").apply()
}
}
}

View File

@@ -1,85 +0,0 @@
package org.isoron.uhabits.notifications;
import android.app.*;
import android.os.*;
import android.support.annotation.*;
import android.support.v4.app.*;
import android.text.format.*;
import android.view.*;
import android.widget.*;
import com.android.datetimepicker.time.TimePickerDialog;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.receivers.*;
import java.util.*;
import static android.content.ContentUris.parseId;
public class SnoozeDelayPickerActivity extends FragmentActivity
implements AdapterView.OnItemClickListener
{
private Habit habit;
private ReminderController reminderController;
@Override
protected void onCreate(@Nullable Bundle bundle)
{
super.onCreate(bundle);
if (getIntent() == null) finish();
if (getIntent().getData() == null) finish();
HabitsApplication app = (HabitsApplication) getApplicationContext();
HabitsApplicationComponent appComponent = app.getComponent();
reminderController = appComponent.getReminderController();
habit = appComponent.getHabitList().getById(parseId(getIntent().getData()));
if (habit == null) finish();
int theme = R.style.Theme_AppCompat_Light_Dialog_Alert;
AlertDialog dialog = new AlertDialog.Builder(new ContextThemeWrapper(this, theme))
.setTitle(R.string.select_snooze_delay)
.setItems(R.array.snooze_picker_names, null)
.create();
dialog.getListView().setOnItemClickListener(this);
dialog.setOnDismissListener(d -> finish());
dialog.show();
}
private void showTimePicker()
{
final Calendar calendar = Calendar.getInstance();
TimePickerDialog dialog = TimePickerDialog.newInstance(
(view, hour, minute) -> {
reminderController.onSnoozeTimePicked(habit, hour, minute);
finish();
},
calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE),
DateFormat.is24HourFormat(this));
dialog.show(getSupportFragmentManager(), "timePicker");
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
int[] snoozeValues = getResources().getIntArray(R.array.snooze_picker_values);
if (snoozeValues[position] >= 0)
{
reminderController.onSnoozeDelayPicked(habit, snoozeValues[position]);
finish();
}
else showTimePicker();
}
@Override
public void finish()
{
super.finish();
overridePendingTransition(0, 0);
}
}

View File

@@ -1,95 +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.preferences
import android.content.*
import android.preference.*
import org.isoron.androidbase.*
import org.isoron.uhabits.R
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.preferences.*
import javax.inject.*
@AppScope
class SharedPreferencesStorage
@Inject constructor(
@AppContext context: Context
) : SharedPreferences.OnSharedPreferenceChangeListener, Preferences.Storage {
private val sharedPrefs: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context)
private var preferences: Preferences? = null
init {
sharedPrefs.registerOnSharedPreferenceChangeListener(this)
PreferenceManager.setDefaultValues(context, R.xml.preferences, false)
}
override fun clear() = sharedPrefs.edit().clear().apply()
override fun getBoolean(key: String, defValue: Boolean) =
sharedPrefs.getBoolean(key, defValue)
override fun getInt(key: String, defValue: Int) =
sharedPrefs.getInt(key, defValue)
override fun getLong(key: String, defValue: Long) =
sharedPrefs.getLong(key, defValue)
override fun getString(key: String, defValue: String): String =
sharedPrefs.getString(key, defValue)
override fun onAttached(preferences: Preferences) {
this.preferences = preferences
}
override fun putBoolean(key: String, value: Boolean) =
sharedPrefs.edit().putBoolean(key, value).apply()
override fun putInt(key: String, value: Int) =
sharedPrefs.edit().putInt(key, value).apply()
override fun putLong(key: String, value: Long) =
sharedPrefs.edit().putLong(key, value).apply()
override fun putString(key: String, value: String) =
sharedPrefs.edit().putString(key, value).apply()
override fun remove(key: String) =
sharedPrefs.edit().remove(key).apply()
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences,
key: String) {
val preferences = this.preferences ?: return
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
when (key) {
"pref_checkmark_reverse_order" ->
preferences.isCheckmarkSequenceReversed = getBoolean(key, false)
"pref_sticky_notifications" ->
preferences.setNotificationsSticky(getBoolean(key, false))
"pref_led_notifications" ->
preferences.setNotificationsLed(getBoolean(key, false))
"pref_feature_sync" ->
preferences.isSyncEnabled = getBoolean(key, false)
}
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
}

View File

@@ -1,38 +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.receivers
import android.content.*
import android.content.Context.*
import android.net.*
import org.isoron.uhabits.*
class ConnectivityReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (context == null || intent == null) return
val app = context.applicationContext as HabitsApplication
val networkInfo = (context.getSystemService(CONNECTIVITY_SERVICE)
as ConnectivityManager).activeNetworkInfo
val isConnected = (networkInfo != null) &&
networkInfo.isConnectedOrConnecting
val syncManager = app.component.syncManager
syncManager.onNetworkStatusChanged(isConnected)
}
}

View File

@@ -1,25 +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.receivers;
import javax.inject.*;
@Scope
public @interface ReceiverScope { }

View File

@@ -1,112 +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.receivers;
import android.content.*;
import android.net.*;
import android.support.annotation.*;
import org.isoron.uhabits.core.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.reminders.*;
import org.isoron.uhabits.core.ui.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.notifications.*;
import javax.inject.*;
@AppScope
public class ReminderController
{
@NonNull
private final ReminderScheduler reminderScheduler;
@NonNull
private final NotificationTray notificationTray;
@NonNull
private Preferences preferences;
@Inject
public ReminderController(@NonNull ReminderScheduler reminderScheduler,
@NonNull NotificationTray notificationTray,
@NonNull Preferences preferences)
{
this.reminderScheduler = reminderScheduler;
this.notificationTray = notificationTray;
this.preferences = preferences;
}
public void onBootCompleted()
{
reminderScheduler.scheduleAll();
}
public void onShowReminder(@NonNull Habit habit,
Timestamp timestamp,
long reminderTime)
{
notificationTray.show(habit, timestamp, reminderTime);
reminderScheduler.scheduleAll();
}
public void onSnoozePressed(@NonNull Habit habit, final Context context)
{
long delay = preferences.getSnoozeInterval();
if (delay < 0)
showSnoozeDelayPicker(habit, context);
else
scheduleReminderMinutesFromNow(habit, delay);
}
public void onSnoozeDelayPicked(Habit habit, int delay)
{
scheduleReminderMinutesFromNow(habit, delay);
}
public void onSnoozeTimePicked(Habit habit, int hour, int minute)
{
Long time = DateUtils.getUpcomingTimeInMillis(hour, minute);
reminderScheduler.scheduleAtTime(habit, time);
notificationTray.cancel(habit);
}
public void onDismiss(@NonNull Habit habit)
{
notificationTray.cancel(habit);
}
private void scheduleReminderMinutesFromNow(Habit habit, long minutes)
{
reminderScheduler.scheduleMinutesFromNow(habit, minutes);
notificationTray.cancel(habit);
}
private void showSnoozeDelayPicker(@NonNull Habit habit, Context context)
{
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
Intent intent = new Intent(context, SnoozeDelayPickerActivity.class);
intent.setData(Uri.parse(habit.getUriString()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}

View File

@@ -1,101 +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.receivers;
import android.content.*;
import android.support.annotation.*;
import android.util.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.utils.*;
import static android.content.ContentUris.*;
/**
* The Android BroadcastReceiver for Loop Habit Tracker.
* <p>
* All broadcast messages are received and processed by this class.
*/
public class ReminderReceiver extends BroadcastReceiver
{
public static final String ACTION_DISMISS_REMINDER =
"org.isoron.uhabits.ACTION_DISMISS_REMINDER";
public static final String ACTION_SHOW_REMINDER =
"org.isoron.uhabits.ACTION_SHOW_REMINDER";
public static final String ACTION_SNOOZE_REMINDER =
"org.isoron.uhabits.ACTION_SNOOZE_REMINDER";
private static final String TAG = "ReminderReceiver";
@Override
public void onReceive(@Nullable final Context context, @Nullable Intent intent)
{
if (context == null || intent == null) return;
if (intent.getAction() == null) return;
HabitsApplication app = (HabitsApplication) context.getApplicationContext();
HabitsApplicationComponent appComponent = app.getComponent();
HabitList habits = appComponent.getHabitList();
ReminderController reminderController = appComponent.getReminderController();
Log.i(TAG, String.format("Received intent: %s", intent.toString()));
Habit habit = null;
long today = DateUtils.getStartOfToday();
if (intent.getData() != null)
habit = habits.getById(parseId(intent.getData()));
final Long timestamp = intent.getLongExtra("timestamp", today);
final Long reminderTime = intent.getLongExtra("reminderTime", today);
try
{
switch (intent.getAction())
{
case ACTION_SHOW_REMINDER:
if (habit == null) return;
reminderController.onShowReminder(habit,
new Timestamp(timestamp), reminderTime);
break;
case ACTION_DISMISS_REMINDER:
if (habit == null) return;
reminderController.onDismiss(habit);
break;
case ACTION_SNOOZE_REMINDER:
if (habit == null) return;
reminderController.onSnoozePressed(habit, context);
break;
case Intent.ACTION_BOOT_COMPLETED:
reminderController.onBootCompleted();
break;
}
}
catch (RuntimeException e)
{
Log.e(TAG, "could not process intent", e);
}
}
}

View File

@@ -1,105 +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.receivers;
import android.content.*;
import android.util.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.ui.widgets.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.sync.*;
import dagger.*;
/**
* The Android BroadcastReceiver for Loop Habit Tracker.
* <p>
* All broadcast messages are received and processed by this class.
*/
public class WidgetReceiver extends BroadcastReceiver
{
public static final String ACTION_ADD_REPETITION =
"org.isoron.uhabits.ACTION_ADD_REPETITION";
public static final String ACTION_DISMISS_REMINDER =
"org.isoron.uhabits.ACTION_DISMISS_REMINDER";
public static final String ACTION_REMOVE_REPETITION =
"org.isoron.uhabits.ACTION_REMOVE_REPETITION";
public static final String ACTION_TOGGLE_REPETITION =
"org.isoron.uhabits.ACTION_TOGGLE_REPETITION";
@Override
public void onReceive(final Context context, Intent intent)
{
HabitsApplication app =
(HabitsApplication) context.getApplicationContext();
WidgetComponent component = DaggerWidgetReceiver_WidgetComponent
.builder()
.habitsApplicationComponent(app.getComponent())
.build();
IntentParser parser = app.getComponent().getIntentParser();
WidgetBehavior controller = component.getWidgetController();
Preferences prefs = app.getComponent().getPreferences();
if(prefs.isSyncEnabled())
context.startService(new Intent(context, SyncService.class));
try
{
IntentParser.CheckmarkIntentData data;
data = parser.parseCheckmarkIntent(intent);
switch (intent.getAction())
{
case ACTION_ADD_REPETITION:
controller.onAddRepetition(data.getHabit(),
data.getTimestamp());
break;
case ACTION_TOGGLE_REPETITION:
controller.onToggleRepetition(data.getHabit(),
data.getTimestamp());
break;
case ACTION_REMOVE_REPETITION:
controller.onRemoveRepetition(data.getHabit(),
data.getTimestamp());
break;
}
}
catch (RuntimeException e)
{
Log.e("WidgetReceiver", "could not process intent", e);
}
}
@ReceiverScope
@Component(dependencies = HabitsApplicationComponent.class)
interface WidgetComponent
{
WidgetBehavior getWidgetController();
}
}

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