Merge branch 'feature/numerical' into dev

This commit is contained in:
2020-06-19 07:50:50 -05:00
48 changed files with 1492 additions and 1812 deletions

View File

@@ -1,229 +1,215 @@
<?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">
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.INTERNET"/>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.isoron.uhabits">
<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"/>
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">
<activity
android:name=".activities.habits.list.ListHabitsActivity"
android:exported="true"
android:label="@string/main_activity_title"
android:launchMode="singleTop"/>
android:exported="true"
android:name=".activities.habits.edit.EditHabitActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity" />
</activity>
<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">
android:name=".MainActivity"
android:label="@string/main_activity_title"
android:launchMode="singleTop"
android:targetActivity=".activities.habits.list.ListHabitsActivity">
<intent-filter android:label="@string/main_activity_title">
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<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">
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"/>
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity" />
</activity>
<activity
android:name=".activities.settings.SettingsActivity"
android:label="@string/settings">
android:name=".activities.settings.SettingsActivity"
android:label="@string/settings">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity"/>
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"/>
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">
android:name=".widgets.HabitPickerDialog"
android:theme="@style/Theme.AppCompat.Light.Dialog">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<activity
android:name=".activities.about.AboutActivity"
android:label="@string/about">
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">
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">
android:name=".widgets.CheckmarkWidgetProvider"
android:label="@string/checkmark">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_checkmark_info"/>
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" />
<service
android:name=".widgets.StackWidgetService"
android:exported="false"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<receiver
android:name=".widgets.HistoryWidgetProvider"
android:label="@string/history">
android:name=".widgets.HistoryWidgetProvider"
android:label="@string/history">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_history_info"/>
android:name="android.appwidget.provider"
android:resource="@xml/widget_history_info" />
</receiver>
<receiver
android:name=".widgets.ScoreWidgetProvider"
android:label="@string/habit_strength">
android:name=".widgets.ScoreWidgetProvider"
android:label="@string/habit_strength">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_score_info"/>
android:name="android.appwidget.provider"
android:resource="@xml/widget_score_info" />
</receiver>
<receiver
android:name=".widgets.StreakWidgetProvider"
android:label="@string/streaks">
android:name=".widgets.StreakWidgetProvider"
android:label="@string/streaks">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_streak_info"/>
android:name="android.appwidget.provider"
android:resource="@xml/widget_streak_info" />
</receiver>
<receiver
android:name=".widgets.FrequencyWidgetProvider"
android:label="@string/frequency">
android:name=".widgets.FrequencyWidgetProvider"
android:label="@string/frequency">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_frequency_info"/>
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"/>
<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"/>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="org.isoron.uhabits.ACTION_TOGGLE_REPETITION"/>
<action android:name="org.isoron.uhabits.ACTION_TOGGLE_REPETITION" />
<data
android:host="org.isoron.uhabits"
android:scheme="content"/>
android:host="org.isoron.uhabits"
android:scheme="content" />
</intent-filter>
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="org.isoron.uhabits.ACTION_ADD_REPETITION"/>
<action android:name="org.isoron.uhabits.ACTION_ADD_REPETITION" />
<data
android:host="org.isoron.uhabits"
android:scheme="content"/>
android:host="org.isoron.uhabits"
android:scheme="content" />
</intent-filter>
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="org.isoron.uhabits.ACTION_REMOVE_REPETITION"/>
<action android:name="org.isoron.uhabits.ACTION_REMOVE_REPETITION" />
<data
android:host="org.isoron.uhabits"
android:scheme="content"/>
android:host="org.isoron.uhabits"
android:scheme="content" />
</intent-filter>
</receiver>
<!-- Locale/Tasker -->
</receiver> <!-- Locale/Tasker -->
<activity
android:name=".automation.EditSettingActivity"
android:exported="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
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"/>
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
</intent-filter>
</activity>
<!-- Locale/Tasker -->
</activity> <!-- Locale/Tasker -->
<receiver
android:name=".automation.FireSettingReceiver"
android:exported="true">
android:name=".automation.FireSettingReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
</intent-filter>
</receiver>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="org.isoron.uhabits"
android:exported="false"
android:grantUriPermissions="true">
android:name="androidx.core.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"/>
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>
android:name=".sync.SyncService"
android:enabled="true"
android:exported="false"></service>
</application>
</manifest>

View File

@@ -19,6 +19,7 @@
package org.isoron.uhabits.activities
import android.app.*
import android.content.res.Configuration.*
import android.os.Build.VERSION.*
import androidx.core.content.*
@@ -30,8 +31,8 @@ import javax.inject.*
@ActivityScope
class AndroidThemeSwitcher
@Inject constructor(
private val activity: BaseActivity,
constructor(
private val activity: Activity,
preferences: Preferences
) : ThemeSwitcher(preferences) {

View File

@@ -48,5 +48,5 @@ interface HabitsActivityComponent {
val listHabitsScreen: ListHabitsScreen
val listHabitsSelectionMenu: ListHabitsSelectionMenu
val showHabitScreen: ShowHabitScreen
val themeSwitcher: AndroidThemeSwitcher
val themeSwitcher: ThemeSwitcher
}

View File

@@ -21,10 +21,17 @@ package org.isoron.uhabits.activities
import dagger.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.*
@Module
abstract class HabitsActivityModule {
@Binds @ActivityScope
internal abstract fun getThemeSwitcher(t: AndroidThemeSwitcher): ThemeSwitcher
class HabitsActivityModule {
@Provides
@ActivityScope
fun getThemeSwitcher(activity: BaseActivity,
prefs: Preferences
): ThemeSwitcher {
return AndroidThemeSwitcher(activity, prefs)
}
}

View File

@@ -0,0 +1,172 @@
/*
* 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.os.*
import android.util.*
import android.view.*
import android.widget.*
import androidx.appcompat.app.*
import androidx.appcompat.app.AlertDialog
import kotlinx.android.synthetic.main.frequency_picker_dialog.view.*
import org.isoron.uhabits.*
class FrequencyPickerDialog(var freqNumerator: Int,
var freqDenominator: Int
) : AppCompatDialogFragment() {
lateinit var contentView: View
var onFrequencyPicked: (num: Int, den: Int) -> Unit = {_,_ -> }
constructor() : this(1, 1)
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val inflater = LayoutInflater.from(activity!!)
contentView = inflater.inflate(R.layout.frequency_picker_dialog, null)
contentView.everyDayRadioButton.setOnClickListener {
check(contentView.everyDayRadioButton)
unfocusAll()
}
contentView.everyXDaysRadioButton.setOnClickListener {
check(contentView.everyXDaysRadioButton)
val everyXDaysTextView = contentView.everyXDaysTextView
focus(everyXDaysTextView)
}
contentView.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
if(hasFocus) check(contentView.everyXDaysRadioButton)
}
contentView.xTimesPerWeekRadioButton.setOnClickListener {
check(contentView.xTimesPerWeekRadioButton)
focus(contentView.xTimesPerWeekTextView)
}
contentView.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
if(hasFocus) check(contentView.xTimesPerWeekRadioButton)
}
contentView.xTimesPerMonthRadioButton.setOnClickListener {
check(contentView.xTimesPerMonthRadioButton)
focus(contentView.xTimesPerMonthTextView)
}
contentView.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
if(hasFocus) check(contentView.xTimesPerMonthRadioButton)
}
return AlertDialog.Builder(activity!!)
.setView(contentView)
.setPositiveButton(R.string.save) { _, _ -> onSaveClicked() }
.create()
}
private fun onSaveClicked() {
var numerator = 1
var denominator = 1
when {
contentView.everyDayRadioButton.isChecked -> {
// NOP
}
contentView.everyXDaysRadioButton.isChecked -> {
if (contentView.everyXDaysTextView.text.isNotEmpty()) {
denominator = Integer.parseInt(contentView.everyXDaysTextView.text.toString())
}
}
contentView.xTimesPerWeekRadioButton.isChecked -> {
if (contentView.xTimesPerWeekTextView.text.isNotEmpty()) {
numerator = Integer.parseInt(contentView.xTimesPerWeekTextView.text.toString())
denominator = 7
}
}
else -> {
if (contentView.xTimesPerMonthTextView.text.isNotEmpty()) {
numerator = Integer.parseInt(contentView.xTimesPerMonthTextView.text.toString())
denominator = 30
}
}
}
if (numerator >= denominator || numerator < 1) {
numerator = 1
denominator = 1
}
onFrequencyPicked(numerator, denominator)
dismiss()
}
private fun check(view: RadioButton?) {
uncheckAll()
view?.isChecked = true
view?.requestFocus()
}
override fun onResume() {
super.onResume()
populateViews()
}
private fun populateViews() {
uncheckAll()
if (freqNumerator == 1) {
if(freqDenominator == 1) {
contentView.everyDayRadioButton.isChecked = true
} else {
contentView.everyXDaysRadioButton.isChecked = true
contentView.everyXDaysTextView.setText(freqDenominator.toString())
focus(contentView.everyXDaysTextView)
}
} else {
if(freqDenominator == 7) {
contentView.xTimesPerWeekRadioButton.isChecked = true
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString())
focus(contentView.xTimesPerWeekTextView)
} else if (freqDenominator == 30 || freqDenominator == 31) {
contentView.xTimesPerMonthRadioButton.isChecked = true
contentView.xTimesPerMonthTextView.setText(freqNumerator.toString())
focus(contentView.xTimesPerMonthTextView)
} else {
Log.w("FrequencyPickerDialog", "Unknown frequency: $freqNumerator/$freqDenominator")
contentView.everyDayRadioButton.isChecked = true
}
}
}
private fun focus(view: EditText) {
view.requestFocus()
view.setSelection(view.text.length)
}
private fun uncheckAll() {
contentView.everyDayRadioButton.isChecked = false
contentView.everyXDaysRadioButton.isChecked = false
contentView.xTimesPerWeekRadioButton.isChecked = false
contentView.xTimesPerMonthRadioButton.isChecked = false
}
private fun unfocusAll() {
contentView.everyXDaysTextView.clearFocus()
contentView.xTimesPerWeekTextView.clearFocus()
contentView.xTimesPerMonthTextView.clearFocus()
}
}

View File

@@ -0,0 +1,250 @@
/*
* 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.content.res.*
import android.graphics.*
import android.os.*
import android.text.format.*
import android.view.*
import androidx.appcompat.app.*
import androidx.fragment.app.*
import com.android.datetimepicker.time.*
import kotlinx.android.synthetic.main.activity_edit_habit.*
import org.isoron.androidbase.utils.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.*
import org.isoron.uhabits.activities.common.dialogs.*
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
class EditHabitActivity : AppCompatActivity() {
private lateinit var themeSwitcher: AndroidThemeSwitcher
private lateinit var binding: ActivityEditHabitBinding
private lateinit var commandRunner: CommandRunner
var habitId = -1L
var paletteColor = 11
var androidColor = 0
var freqNum = 1
var freqDen = 1
var reminderHour = -1
var reminderMin = -1
var reminderDays: WeekdayList = WeekdayList.EVERY_DAY
override fun onCreate(state: Bundle?) {
super.onCreate(state)
val component = (application as HabitsApplication).component
themeSwitcher = AndroidThemeSwitcher(this, component.preferences)
themeSwitcher.apply()
binding = ActivityEditHabitBinding.inflate(layoutInflater)
setContentView(binding.root)
if (intent.hasExtra("habitId")) {
binding.toolbar.title = getString(R.string.edit_habit)
habitId = intent.getLongExtra("habitId", -1)
val habit = component.habitList.getById(habitId)!!
paletteColor = habit.color
freqNum = habit.frequency.numerator
freqDen = habit.frequency.denominator
if (habit.hasReminder()) {
reminderHour = habit.reminder.hour
reminderMin = habit.reminder.minute
reminderDays = habit.reminder.days
}
binding.nameInput.setText(habit.name)
binding.questionInput.setText(habit.question)
binding.notesInput.setText(habit.description)
}
if (state != null) {
habitId = state.getLong("habitId")
paletteColor = state.getInt("paletteColor")
freqNum = state.getInt("freqNum")
freqDen = state.getInt("freqDen")
reminderHour = state.getInt("reminderHour")
reminderMin = state.getInt("reminderMin")
reminderDays = WeekdayList(state.getInt("reminderDays"))
}
updateColors()
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.elevation = 10.0f
val colorPickerDialogFactory = ColorPickerDialogFactory(this)
binding.colorButton.setOnClickListener {
val dialog = colorPickerDialogFactory.create(paletteColor)
dialog.setListener { paletteColor ->
this.paletteColor = paletteColor
updateColors()
}
dialog.show(supportFragmentManager, "colorPicker")
}
populateFrequency()
binding.frequencyPicker.setOnClickListener {
val dialog = FrequencyPickerDialog(freqNum, freqDen)
dialog.onFrequencyPicked = { num, den ->
freqNum = num
freqDen = den
populateFrequency()
}
dialog.show(supportFragmentManager, "frequencyPicker")
}
populateReminder()
binding.reminderTimePicker.setOnClickListener {
val currentHour = if (reminderHour >= 0) reminderHour else 8
val currentMin = if (reminderMin >= 0) reminderMin else 0
val is24HourMode = DateFormat.is24HourFormat(this)
val dialog = TimePickerDialog.newInstance(object : TimePickerDialog.OnTimeSetListener {
override fun onTimeSet(view: RadialPickerLayout?, hourOfDay: Int, minute: Int) {
reminderHour = hourOfDay
reminderMin = minute
populateReminder()
}
override fun onTimeCleared(view: RadialPickerLayout?) {
reminderHour = -1
reminderMin = -1
reminderDays = WeekdayList.EVERY_DAY
populateReminder()
}
}, currentHour, currentMin, is24HourMode, androidColor)
dialog.show(supportFragmentManager, "timePicker")
}
binding.reminderDatePicker.setOnClickListener {
val dialog = WeekdayPickerDialog()
dialog.setListener { days ->
reminderDays = days
if (reminderDays.isEmpty) reminderDays = WeekdayList.EVERY_DAY
populateReminder()
}
dialog.setSelectedDays(reminderDays)
dialog.show(supportFragmentManager, "dayPicker")
}
binding.buttonSave.setOnClickListener {
if(validate()) save()
}
for (fragment in supportFragmentManager.fragments) {
(fragment as DialogFragment).dismiss()
}
}
private fun save() {
val component = (application as HabitsApplication).component
val habit = component.modelFactory.buildHabit()
var original: Habit? = null
if (habitId >= 0) {
original = component.habitList.getById(habitId)!!
habit.copyFrom(original)
}
habit.name = nameInput.text.trim().toString()
habit.question = questionInput.text.trim().toString()
habit.description = notesInput.text.trim().toString()
habit.color = paletteColor
if (reminderHour >= 0) {
habit.setReminder(Reminder(reminderHour, reminderMin, reminderDays))
}
habit.frequency = Frequency(freqNum, freqDen)
habit.unit = ""
habit.targetValue = 1.0
habit.type = Habit.YES_NO_HABIT
val command = if (habitId >= 0) {
component.editHabitCommandFactory.create(component.habitList, original, habit)
} else {
component.createHabitCommandFactory.create(component.habitList, habit)
}
component.commandRunner.execute(command, null)
finish()
}
private fun validate(): Boolean {
if (nameInput.text.isEmpty()) {
nameInput.error = getString(R.string.validation_name_should_not_be_blank)
return false
}
return true
}
private fun populateReminder() {
if (reminderHour < 0) {
binding.reminderTimePicker.text = getString(R.string.reminder_off)
binding.reminderDatePicker.visibility = View.GONE
binding.reminderDivider.visibility = View.GONE
} else {
val time = AndroidDateUtils.formatTime(this, reminderHour, reminderMin)
val daysArray = reminderDays.toArray()
binding.reminderTimePicker.text = time
binding.reminderDatePicker.visibility = View.VISIBLE
binding.reminderDivider.visibility = View.VISIBLE
binding.reminderDatePicker.text = AndroidDateUtils.formatWeekdayList(this, daysArray)
}
}
private fun populateFrequency() {
val label = when {
freqNum == 1 && freqDen == 1 -> getString(R.string.every_day)
freqNum == 1 && freqDen == 7 -> getString(R.string.every_week)
freqNum == 1 && freqDen > 1 -> getString(R.string.every_x_days, freqDen)
freqDen == 7 -> getString(R.string.x_times_per_week, freqNum)
freqDen == 31 -> getString(R.string.x_times_per_month, freqNum)
else -> "Unknown"
}
binding.frequencyPicker.text = label
}
private fun updateColors() {
androidColor = PaletteUtils.getColor(this, paletteColor)
binding.colorButton.backgroundTintList = ColorStateList.valueOf(androidColor)
if (!themeSwitcher.isNightMode) {
val darkerAndroidColor = ColorUtils.mixColors(Color.BLACK, androidColor, 0.15f)
window.statusBarColor = darkerAndroidColor
binding.toolbar.setBackgroundColor(androidColor)
}
}
override fun onSaveInstanceState(state: Bundle) {
super.onSaveInstanceState(state)
with(state) {
putLong("habitId", habitId)
putInt("paletteColor", paletteColor)
putInt("androidColor", androidColor)
putInt("freqNum", freqNum)
putInt("freqDen", freqDen)
putInt("reminderHour", reminderHour)
putInt("reminderMin", reminderMin)
putInt("reminderDays", reminderDays.toInteger())
}
}
}

View File

@@ -1,290 +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.*;
import android.content.*;
import android.os.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.*;
import android.text.format.*;
import android.view.*;
import com.android.datetimepicker.time.TimePickerDialog;
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.*;
public class EditHabitDialog extends AppCompatDialogFragment
{
public static final String BUNDLE_HABIT_ID = "habitId";
public static final String BUNDLE_HABIT_TYPE = "habitType";
private static final String WEEKDAY_PICKER_TAG = "weekdayPicker";
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()
{
HabitsActivity activity = (HabitsActivity) getActivity();
return activity.getComponent().getThemeSwitcher().getDialogTheme();
}
@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();
restoreChildFragmentListeners();
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.setQuestion(namePanel.getQuestion());
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(getChildFragmentManager(), WEEKDAY_PICKER_TAG);
}
});
}
private void restoreChildFragmentListeners()
{
final WeekdayPickerDialog dialog =
(WeekdayPickerDialog) getChildFragmentManager()
.findFragmentByTag(WEEKDAY_PICKER_TAG);
if(dialog != null) dialog.setListener(reminderPanel);
}
}

View File

@@ -1,69 +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 androidx.annotation.NonNull;
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

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2016-2020 Á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.view.*
import androidx.appcompat.app.*
import org.isoron.uhabits.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.intents.*
class HabitTypeDialog : AppCompatDialogFragment() {
override fun getTheme() = R.style.Translucent
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val binding = SelectHabitTypeBinding.inflate(inflater, container, false)
binding.buttonYesNo.setOnClickListener {
val intent = IntentFactory().startEditActivity(activity!!)
startActivity(intent)
dismiss()
}
binding.buttonMeasurable.setOnClickListener {
dismiss()
}
binding.buttonSubjective.setOnClickListener{
dismiss()
}
binding.background.setOnClickListener {
dismiss()
}
return binding.root
}
}

View File

@@ -1,116 +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.text.*;
import android.util.*;
import android.view.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatEditText;
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 AppCompatEditText
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(@NonNull 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,166 +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.util.*;
import android.view.*;
import android.widget.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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,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.content.*;
import android.content.res.*;
import android.os.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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.tvQuestion)
ExampleEditText tvQuestion;
@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 getQuestion()
{
return tvQuestion.getRealText().trim();
}
@NonNull
public String getName()
{
return tvName.getText().toString().trim();
}
public void populateFrom(@NonNull Habit habit)
{
Resources res = getResources();
if(habit.isNumerical())
tvQuestion.setExample(res.getString(R.string.example_question_numerical));
else
tvQuestion.setExample(res.getString(R.string.example_question_boolean));
setColor(habit.getColor());
tvName.setText(habit.getName());
tvQuestion.setRealText(habit.getQuestion());
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,197 +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.util.*;
import android.view.*;
import android.widget.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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,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.activities.habits.edit.views;
import android.content.*;
import android.content.res.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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

@@ -39,9 +39,7 @@ class ListHabitsMenu @Inject constructor(
val nightModeItem = menu.findItem(R.id.actionToggleNightMode)
val hideArchivedItem = menu.findItem(R.id.actionHideArchived)
val hideCompletedItem = menu.findItem(R.id.actionHideCompleted)
val addNumericalHabit = menu.findItem(R.id.actionCreateNumeralHabit)
addNumericalHabit.isVisible = preferences.isDeveloper
nightModeItem.isChecked = themeSwitcher.isNightMode
hideArchivedItem.isChecked = !preferences.showArchived
hideCompletedItem.isChecked = !preferences.showCompleted
@@ -54,13 +52,8 @@ class ListHabitsMenu @Inject constructor(
return true
}
R.id.actionCreateBooleanHabit -> {
behavior.onCreateBooleanHabit()
return true
}
R.id.actionCreateNumeralHabit -> {
behavior.onCreateNumericalHabit()
R.id.actionCreateHabit -> {
behavior.onCreateHabit()
return true
}

View File

@@ -58,14 +58,12 @@ class ListHabitsScreen
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>,
@@ -137,14 +135,9 @@ class ListHabitsScreen
activity.startActivity(intent)
}
override fun showCreateBooleanHabitScreen() {
val dialog = editHabitDialogFactory.createBoolean()
activity.showDialog(dialog, "editHabit")
}
override fun showCreateNumericalHabitScreen() {
val dialog = editHabitDialogFactory.createNumerical()
activity.showDialog(dialog, "editHabit")
override fun showSelectHabitTypeDialog() {
val dialog = HabitTypeDialog()
activity.showDialog(dialog, "habitType")
}
override fun showDeleteConfirmationScreen(callback: OnConfirmedCallback) {
@@ -152,8 +145,8 @@ class ListHabitsScreen
}
override fun showEditHabitsScreen(habits: List<Habit>) {
val dialog = editHabitDialogFactory.edit(habits[0])
activity.showDialog(dialog, "editNumericalHabit")
val intent = intentFactory.startEditActivity(activity!!, habits[0])
activity.startActivity(intent)
}
override fun showFAQScreen() {

View File

@@ -19,6 +19,8 @@
package org.isoron.uhabits.activities.habits.show;
import android.content.*;
import androidx.annotation.NonNull;
import org.isoron.androidbase.activities.*;
@@ -28,6 +30,7 @@ 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 org.isoron.uhabits.intents.*;
import javax.inject.*;
@@ -43,30 +46,30 @@ public class ShowHabitScreen extends BaseScreen
@NonNull
private final Habit habit;
@NonNull
private final EditHabitDialogFactory editHabitDialogFactory;
@NonNull
private final ConfirmDeleteDialogFactory confirmDeleteDialogFactory;
private final Lazy<ShowHabitBehavior> behavior;
@NonNull
private final IntentFactory intentFactory;
@Inject
public ShowHabitScreen(@NonNull BaseActivity activity,
@NonNull Habit habit,
@NonNull ShowHabitRootView view,
@NonNull ShowHabitsMenu menu,
@NonNull EditHabitDialogFactory editHabitDialogFactory,
@NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory,
@NonNull IntentFactory intentFactory,
@NonNull Lazy<ShowHabitBehavior> behavior)
{
super(activity);
this.intentFactory = intentFactory;
setMenu(menu);
setRootView(view);
this.habit = habit;
this.behavior = behavior;
this.editHabitDialogFactory = editHabitDialogFactory;
this.confirmDeleteDialogFactory = confirmDeleteDialogFactory;
view.setController(this);
}
@@ -102,7 +105,8 @@ public class ShowHabitScreen extends BaseScreen
@Override
public void showEditHabitScreen(@NonNull Habit habit)
{
activity.showDialog(editHabitDialogFactory.edit(habit), "editHabit");
Intent intent = intentFactory.startEditActivity(activity, habit);
activity.startActivity(intent);
}
@Override

View File

@@ -21,8 +21,10 @@ package org.isoron.uhabits.intents
import android.content.*
import android.net.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.about.*
import org.isoron.uhabits.activities.habits.edit.*
import org.isoron.uhabits.activities.habits.show.*
import org.isoron.uhabits.activities.intro.*
import org.isoron.uhabits.activities.settings.*
@@ -81,4 +83,14 @@ class IntentFactory
fun codeContributors(context: Context) =
buildViewIntent(context.getString(R.string.codeContributorsURL))
fun startEditActivity(context: Context): Intent {
return Intent(context, EditHabitActivity::class.java)
}
fun startEditActivity(context: Context, habit: Habit): Intent {
val intent = startEditActivity(context)
intent.putExtra("habitId", habit.id)
return intent
}
}

View File

@@ -2,6 +2,7 @@ package org.isoron.uhabits.notifications;
import android.app.*;
import android.graphics.*;
import android.os.*;
import androidx.annotation.Nullable;
@@ -66,7 +67,8 @@ public class SnoozeDelayPickerActivity extends FragmentActivity
},
calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE),
DateFormat.is24HourFormat(this));
DateFormat.is24HourFormat(this),
Color.BLUE);
dialog.show(getSupportFragmentManager(), "timePicker");
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/highContrastReverseTextColor"/>
<stroke android:width="1dp" android:color="?attr/lowContrastTextColor" />
<corners android:radius="4dp"/>
<padding
android:left="8dp"
android:top="8dp"
android:right="8dp"
android:bottom="8dp" />
</shape>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/highContrastReverseTextColor"/>
<stroke android:width="1dp" android:color="?attr/lowContrastTextColor" />
<corners android:radius="4dp"/>
<padding
android:left="0dp"
android:top="8dp"
android:right="0dp"
android:bottom="0dp" />
</shape>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/mediumContrastTextColor"
android:pathData="M7,10l5,5 5,-5z"/>
</vector>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016-2020 Á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/>.
-->
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?colorAccent">
<item android:gravity="center">
<shape android:shape="rectangle">
<solid android:color="?cardBgColor"/>
<corners android:radius="5dp" />
</shape>
</item>
</ripple>

View File

@@ -0,0 +1,220 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/highContrastReverseTextColor"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".activities.habits.edit.EditHabitActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:elevation="2dp"
android:gravity="end"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:title="@string/create_habit"
app:titleTextColor="@color/white">
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonSave"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginEnd="16dp"
android:text="@string/save"
android:textColor="@color/white"
app:rippleColor="@color/white"
app:strokeColor="@color/white" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="8dp">
<!-- Title and color -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- Habit Title -->
<FrameLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="8dp">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/name" />
<EditText
android:id="@+id/nameInput"
style="@style/FormInput"
android:maxLines="1"
android:inputType="textCapSentences"
android:hint="@string/exercise_habit_name"
/>
</LinearLayout>
</FrameLayout>
<!-- Habit Color -->
<FrameLayout
android:layout_width="80dp"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="8dp"
android:paddingStart="0dp">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="Color" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/colorButton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="8dp"
android:backgroundTint="#E23673" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
<!-- Habit Question -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="8dp"
android:paddingTop="4dp">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/question" />
<EditText
android:id="@+id/questionInput"
style="@style/FormInput"
android:inputType="textCapSentences|textMultiLine"
android:hint="@string/example_question_boolean"
/>
</LinearLayout>
</FrameLayout>
<!-- Frequency -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="8dp"
android:paddingTop="4dp">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/frequency" />
<TextView
style="@style/FormDropdown"
android:id="@+id/frequencyPicker"
android:textColor="?attr/highContrastTextColor"
/>
</LinearLayout>
</FrameLayout>
<!-- Reminder -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="8dp">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/reminder" />
<TextView
style="@style/FormDropdown"
android:id="@+id/reminderTimePicker"
android:text="@string/reminder_off" />
<View
style="@style/FormDivider"
android:id="@+id/reminderDivider"/>
<TextView
style="@style/FormDropdown"
android:id="@+id/reminderDatePicker"
android:text="" />
</LinearLayout>
</FrameLayout>
<!-- Notes -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="8dp">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/notes" />
<EditText
android:id="@+id/notesInput"
style="@style/FormInput"
android:inputType="textCapSentences|textMultiLine"
android:hint="@string/example_notes" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
</ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,82 +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/>.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/container"
style="@style/dialogForm"
tools:context=".activities.habits.edit.EditHabitDialog"
tools:ignore="MergeRootFrame">
<LinearLayout
android:id="@+id/formPanel"
style="@style/dialogFormPanel">
<org.isoron.uhabits.activities.habits.edit.views.NameDescriptionPanel
android:id="@+id/namePanel"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<org.isoron.uhabits.activities.habits.edit.views.FrequencyPanel
android:id="@+id/frequencyPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<org.isoron.uhabits.activities.habits.edit.views.TargetPanel
android:id="@+id/targetPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<org.isoron.uhabits.activities.habits.edit.views.ReminderPanel
android:id="@+id/reminderPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:paddingStart="0dp"
android:paddingLeft="0dp"
android:paddingEnd="16dp"
android:paddingRight="16dp">
<Button
android:id="@+id/buttonDiscard"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/discard" />
<Button
android:id="@+id/buttonSave"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/save" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -1,69 +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/>.
-->
<LinearLayout style="@style/dialogFormRow"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="horizontal">
<TextView
style="@style/dialogFormLabel"
android:text="@string/repeat"/>
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="25dp"
android:entries="@array/frequencyQuickSelect"
android:minWidth="400dp"
android:theme="@style/dialogFormText"
android:visibility="gone"/>
<org.apmem.tools.layouts.FlowLayout
android:id="@+id/customFreqPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="fill"
android:visibility="visible">
<EditText
android:id="@+id/numerator"
style="@style/dialogFormInputLargeNumber"/>
<TextView
style="@style/dialogFormText"
android:gravity="center"
android:text="@string/times_every"/>
<EditText
android:id="@+id/denominator"
style="@style/dialogFormInputLargeNumber"/>
<TextView
style="@style/dialogFormText"
android:gravity="center_vertical"
android:paddingLeft="12dp"
android:text="@string/days"/>
</org.apmem.tools.layouts.FlowLayout>
</LinearLayout>

View File

@@ -1,98 +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/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://isoron.org/android"
xmlns:app1="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="300dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilName"
android:layout_width="0dp"
android:layout_height="wrap_content"
app1:layout_constraintEnd_toStartOf="@+id/buttonPickColor"
app1:layout_constraintHorizontal_weight="6"
app1:layout_constraintStart_toStartOf="parent"
app1:layout_constraintTop_toTopOf="parent">
<EditText
android:id="@+id/tvName"
style="@style/dialogFormInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="start"
android:gravity="center_vertical"
android:hint="@string/name">
<requestFocus />
</EditText>
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/buttonPickColor"
style="@style/dialogFormInputColor"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:contentDescription="@string/color_picker_default_title"
android:src="?dialogIconChangeColor"
app1:layout_constraintBottom_toBottomOf="@id/tilName"
app1:layout_constraintEnd_toEndOf="parent"
app1:layout_constraintHorizontal_weight="1"
app1:layout_constraintStart_toEndOf="@+id/tilName"
app1:layout_constraintTop_toTopOf="@id/tilName" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilQuestion"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/question"
app1:layout_constraintEnd_toEndOf="parent"
app1:layout_constraintStart_toStartOf="parent"
app1:layout_constraintTop_toBottomOf="@id/tilName">
<org.isoron.uhabits.activities.habits.edit.views.ExampleEditText
android:id="@+id/tvQuestion"
style="@style/dialogFormInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:example="@string/example_question_numerical" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/notes"
app1:layout_constraintBottom_toBottomOf="parent"
app1:layout_constraintEnd_toEndOf="parent"
app1:layout_constraintStart_toStartOf="parent"
app1:layout_constraintTop_toBottomOf="@id/tilQuestion">
<org.isoron.uhabits.activities.habits.edit.views.ExampleEditText
android:id="@+id/tvDescription"
style="@style/dialogFormInputMultiline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
app:example="@string/example_notes" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,52 +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/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
style="@style/dialogFormRow"
android:layout_marginTop="12dp">
<TextView
style="@style/dialogFormLabel"
android:text="@string/reminder"/>
<TextView
android:id="@+id/tvReminderTime"
style="@style/dialogFormSpinner"/>
</LinearLayout>
<LinearLayout
android:id="@+id/llReminderDays"
style="@style/dialogFormRow"
android:layout_marginTop="12dp">
<TextView
style="@style/dialogFormLabel"
android:text=""/>
<TextView
android:id="@+id/tvReminderDays"
style="@style/dialogFormSpinner"/>
</LinearLayout>
</LinearLayout>

View File

@@ -1,62 +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/>.
-->
<LinearLayout style="@style/dialogFormRow"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://isoron.org/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
style="@style/dialogFormLabel"
android:text="@string/target"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2">
<EditText
android:id="@+id/tvTargetCount"
style="@style/dialogFormInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/count"
android:inputType="numberDecimal"
android:text="@string/default_count"
/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2">
<org.isoron.uhabits.activities.habits.edit.views.ExampleEditText
android:id="@+id/tvUnit"
style="@style/dialogFormInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/unit"
android:inputType="text"
app:example="@string/example_units"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="0dp"
android:paddingTop="16dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical">
<RadioButton
android:id="@+id/everyDayRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/every_day" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical">
<RadioButton
android:id="@+id/everyXDaysRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Every" />
<EditText
android:id="@+id/everyXDaysTextView"
android:layout_width="50dp"
android:layout_height="40dp"
android:gravity="center"
android:background="@drawable/bg_input_box"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:inputType="number"
android:maxLength="2"
android:text="3" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="days" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical">
<RadioButton
android:id="@+id/xTimesPerWeekRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/xTimesPerWeekTextView"
android:layout_width="50dp"
android:layout_height="40dp"
android:gravity="center"
android:background="@drawable/bg_input_box"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:inputType="number"
android:maxLength="2"
android:text="5" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="times per week" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical">
<RadioButton
android:id="@+id/xTimesPerMonthRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/xTimesPerMonthTextView"
android:layout_width="50dp"
android:layout_height="40dp"
android:gravity="center"
android:background="@drawable/bg_input_box"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:inputType="number"
android:maxLength="2"
android:text="10" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="times per month" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016-2020 Á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/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#a0000000"
android:clickable="true"
android:id="@+id/background"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/buttonYesNo"
style="@style/SelectHabitTypeButton">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SelectHabitTypeButtonTitle"
android:text="@string/yes_or_no" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SelectHabitTypeButtonBody"
android:text="@string/yes_or_no_example" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/buttonMeasurable"
style="@style/SelectHabitTypeButton">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SelectHabitTypeButtonTitle"
android:text="@string/measurable" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SelectHabitTypeButtonBody"
android:text="@string/measurable_example" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/buttonSubjective"
style="@style/SelectHabitTypeButton">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SelectHabitTypeButtonTitle"
android:text="@string/subjective" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SelectHabitTypeButtonBody"
android:text="@string/subjective_example" />
</LinearLayout>
</LinearLayout>

View File

@@ -23,7 +23,7 @@
tools:context=".MainActivity">
<item
android:id="@+id/actionCreateBooleanHabit"
android:id="@+id/actionCreateHabit"
android:enabled="true"
android:icon="?iconAdd"
android:title="@string/add_habit"
@@ -96,11 +96,4 @@
android:title="@string/about"
app:showAsAction="never"/>
<item
android:id="@+id/actionCreateNumeralHabit"
android:enabled="true"
android:title="Add Numerical Habit"
android:orderInCategory="200"
app:showAsAction="never" />
</menu>

View File

@@ -245,6 +245,15 @@
<string name="first_day_of_the_week">First day of the week</string>
<string name="default_reminder_question">Have you completed this habit today?</string>
<string name="notes">Notes</string>
<string name="example_notes">You can put whatever you want here!</string>
<string name="example_notes">(Optional)</string>
<string name="yes_or_no_example">e.g. Did you wake up early today? Did you exercise? Did you play chess?</string>
<string name="measurable">Measurable</string>
<string name="measurable_example">e.g. How many miles did you run today? How many pages did you read? How many calories did you eat?</string>
<string name="subjective">Subjective</string>
<string name="subjective_example">e.g. Are you felling happy today? Definitely, somewhat, not at all? How frequently did you snack? Very often, sometimes, never?</string>
<string name="x_times_per_week">%d times per week</string>
<string name="x_times_per_month">%d times per month</string>
<string name="exercise_habit_name">e.g. Exercise</string>
</resources>

View File

@@ -18,10 +18,11 @@
-->
<resources>
<style name="AppBaseTheme" parent="@style/Theme.AppCompat.Light.NoActionBar">
<style name="AppBaseTheme" parent="@style/Theme.MaterialComponents.Light.NoActionBar">
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
<item name="android:dialogTheme">@style/Theme.AppCompat.Light.Dialog</item>
<item name="android:alertDialogTheme">@style/Theme.AppCompat.Light.Dialog</item>
<item name="android:navigationBarColor">?attr/colorPrimary</item>
<item name="windowActionModeOverlay">true</item>
<item name="actionModeBackground">@color/blue_grey_700</item>
@@ -30,9 +31,9 @@
<item name="selectedBackground">@drawable/selected_box_light</item>
<item name="cardBackground">@drawable/card_light_background</item>
<item name="colorPrimary">@color/blue_grey_800</item>
<item name="colorPrimaryDark">@color/blue_grey_900</item>
<item name="colorAccent">?aboutScreenColor</item>
<item name="colorPrimary">#363636</item>
<item name="colorPrimaryDark">#303030</item>
<item name="colorAccent">#303030</item>
<item name="cardBgColor">@color/grey_50</item>
<item name="windowBackgroundColor">@color/grey_200</item>
<item name="headerBackgroundColor">@color/grey_200</item>
@@ -74,7 +75,7 @@
<item name="android:textSize">@dimen/smallTextSize</item>
</style>
<style name="AppBaseThemeDark" parent="@style/Theme.AppCompat.NoActionBar">
<style name="AppBaseThemeDark" parent="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
<item name="android:dialogTheme">@style/Theme.AppCompat.Dialog</item>
<item name="android:alertDialogTheme">@style/Theme.AppCompat.Dialog</item>
@@ -256,4 +257,86 @@
<style name="ScrollableRecyclerViewStyle" parent="android:Widget">
<item name="android:scrollbars">vertical</item>
</style>
<style name="SelectHabitTypeButton">
<item name="android:orientation">vertical</item>
<item name="android:paddingTop">14dp</item>
<item name="android:paddingBottom">16dp</item>
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">16dp</item>
<item name="android:layout_marginLeft">16dp</item>
<item name="android:layout_marginRight">16dp</item>
<item name="android:layout_marginBottom">8dp</item>
<item name="android:layout_marginTop">8dp</item>
<item name="android:elevation">6dp</item>
<item name="android:background">@drawable/round_ripple</item>
<item name="android:clickable">true</item>
<item name="android:selectable">true</item>
</style>
<style name="SelectHabitTypeButtonTitle">
<item name="android:textSize">20sp</item>
<item name="android:textStyle">bold</item>
<item name="android:layout_marginBottom">8dp</item>
</style>
<style name="SelectHabitTypeButtonBody">
<item name="android:textSize">@dimen/smallTextSize</item>
<item name="android:lineSpacingMultiplier">1.25</item>
</style>
<style name="Translucent">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowTranslucentStatus">true</item>
</style>
<style name="FormLabel">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginStart">8dp</item>
<item name="android:layout_marginTop">-17dp</item>
<item name="android:layout_marginBottom">-4dp</item>
<item name="android:background">?attr/highContrastReverseTextColor</item>
<item name="android:paddingStart">8dp</item>
<item name="android:paddingEnd">8dp</item>
<item name="android:textSize">@dimen/smallerTextSize</item>
</style>
<style name="FormInput">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:background">@color/transparent</item>
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">16dp</item>
<item name="android:paddingTop">16dp</item>
<item name="android:paddingBottom">16dp</item>
<item name="android:textSize">@dimen/regularTextSize</item>
</style>
<style name="FormDropdown">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:drawableEnd">@drawable/ic_arrow_drop_down_dark</item>
<item name="android:padding">16dp</item>
<item name="android:text">@string/every_day</item>
<item name="android:textSize">@dimen/regularTextSize</item>
</style>
<style name="FormInnerBox">
<item name="android:background">@drawable/bg_input_group</item>
<item name="android:clipChildren">false</item>
<item name="android:clipToPadding">false</item>
<item name="android:orientation">vertical</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
</style>
<style name="FormDivider">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">1dp</item>
<item name="android:background">?attr/lowContrastTextColor</item>
</style>
</resources>