HistoryChart: Fix HistoryEditorDialog
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 33 KiB |
@@ -177,4 +177,8 @@ class AndroidCanvas : Canvas {
|
|||||||
val bmp = innerBitmap ?: throw UnsupportedOperationException()
|
val bmp = innerBitmap ?: throw UnsupportedOperationException()
|
||||||
return AndroidImage(bmp)
|
return AndroidImage(bmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun measureText(text: String): Double {
|
||||||
|
return textPaint.measureText(text) / innerDensity
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,24 @@ class AndroidDataView(
|
|||||||
override fun onTouchEvent(event: MotionEvent?) = detector.onTouchEvent(event)
|
override fun onTouchEvent(event: MotionEvent?) = detector.onTouchEvent(event)
|
||||||
override fun onDown(e: MotionEvent?) = true
|
override fun onDown(e: MotionEvent?) = true
|
||||||
override fun onShowPress(e: MotionEvent?) = Unit
|
override fun onShowPress(e: MotionEvent?) = Unit
|
||||||
override fun onSingleTapUp(e: MotionEvent?) = false
|
|
||||||
|
override fun onSingleTapUp(e: MotionEvent?): Boolean {
|
||||||
|
val x: Float
|
||||||
|
val y: Float
|
||||||
|
try {
|
||||||
|
val pointerId = e!!.getPointerId(0)
|
||||||
|
x = e.getX(pointerId)
|
||||||
|
y = e.getY(pointerId)
|
||||||
|
} catch (ex: RuntimeException) {
|
||||||
|
// Android often throws IllegalArgumentException here. Apparently,
|
||||||
|
// the pointer id may become invalid shortly after calling
|
||||||
|
// e.getPointerId.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
override fun onLongPress(e: MotionEvent?) = Unit
|
override fun onLongPress(e: MotionEvent?) = Unit
|
||||||
|
|
||||||
override fun onScroll(
|
override fun onScroll(
|
||||||
@@ -76,7 +93,16 @@ class AndroidDataView(
|
|||||||
velocityX: Float,
|
velocityX: Float,
|
||||||
velocityY: Float,
|
velocityY: Float,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
scroller.fling(scroller.currX, scroller.currY, velocityX.toInt() / 2, 0, 0, 10000, 0, 0)
|
scroller.fling(
|
||||||
|
scroller.currX,
|
||||||
|
scroller.currY,
|
||||||
|
velocityX.toInt() / 2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
Integer.MAX_VALUE,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
invalidate()
|
invalidate()
|
||||||
scrollAnimator.duration = scroller.duration.toLong()
|
scrollAnimator.duration = scroller.duration.toLong()
|
||||||
scrollAnimator.start()
|
scrollAnimator.start()
|
||||||
@@ -99,11 +125,14 @@ class AndroidDataView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDataOffset() {
|
private fun updateDataOffset() {
|
||||||
var newDataOffset: Int = scroller.currX / (view.dataColumnWidth * canvas.innerDensity).toInt()
|
view?.let { v ->
|
||||||
newDataOffset = Math.max(0, newDataOffset)
|
var newDataOffset: Int =
|
||||||
if (newDataOffset != view.dataOffset) {
|
scroller.currX / (v.dataColumnWidth * canvas.innerDensity).toInt()
|
||||||
view.dataOffset = newDataOffset
|
newDataOffset = Math.max(0, newDataOffset)
|
||||||
postInvalidate()
|
if (newDataOffset != v.dataOffset) {
|
||||||
|
v.dataOffset = newDataOffset
|
||||||
|
postInvalidate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ open class AndroidView<T : View>(
|
|||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
) : android.view.View(context, attrs) {
|
) : android.view.View(context, attrs) {
|
||||||
|
|
||||||
lateinit var view: T
|
var view: T? = null
|
||||||
val canvas = AndroidCanvas()
|
val canvas = AndroidCanvas()
|
||||||
|
|
||||||
override fun onDraw(canvas: android.graphics.Canvas) {
|
override fun onDraw(canvas: android.graphics.Canvas) {
|
||||||
@@ -36,6 +36,6 @@ open class AndroidView<T : View>(
|
|||||||
this.canvas.innerWidth = width
|
this.canvas.innerWidth = width
|
||||||
this.canvas.innerHeight = height
|
this.canvas.innerHeight = height
|
||||||
this.canvas.innerDensity = resources.displayMetrics.density.toDouble()
|
this.canvas.innerDensity = resources.displayMetrics.density.toDouble()
|
||||||
view.draw(this.canvas)
|
view?.draw(this.canvas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,203 +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.util.*;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.*;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
|
||||||
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.callbacks.*;
|
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.*;
|
|
||||||
import org.isoron.uhabits.core.ui.views.*;
|
|
||||||
import org.isoron.uhabits.utils.*;
|
|
||||||
import org.jetbrains.annotations.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class HistoryEditorDialog extends AppCompatDialogFragment
|
|
||||||
implements DialogInterface.OnClickListener, CommandRunner.Listener
|
|
||||||
{
|
|
||||||
@Nullable
|
|
||||||
private Habit habit;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
HistoryChart historyChart;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private OnToggleCheckmarkListener onToggleCheckmarkListener;
|
|
||||||
|
|
||||||
private HabitList habitList;
|
|
||||||
|
|
||||||
private TaskRunner taskRunner;
|
|
||||||
|
|
||||||
private Preferences prefs;
|
|
||||||
|
|
||||||
private CommandRunner commandRunner;
|
|
||||||
|
|
||||||
public HistoryEditorDialog()
|
|
||||||
{
|
|
||||||
this.onToggleCheckmarkListener = new OnToggleCheckmarkListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onToggleEntry(@NotNull Timestamp timestamp, int value)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@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();
|
|
||||||
commandRunner = app.getComponent().getCommandRunner();
|
|
||||||
prefs = app.getComponent().getPreferences();
|
|
||||||
|
|
||||||
// historyChart = new HistoryChart(context);
|
|
||||||
// historyChart.setOnToggleCheckmarkListener(onToggleCheckmarkListener);
|
|
||||||
// historyChart.setFirstWeekday(prefs.getFirstWeekday());
|
|
||||||
// historyChart.setSkipEnabled(prefs.isSkipEnabled());
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
commandRunner.addListener(this);
|
|
||||||
refreshData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause()
|
|
||||||
{
|
|
||||||
commandRunner.removeListener(this);
|
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState)
|
|
||||||
{
|
|
||||||
// outState.putLong("habit", habit.getId());
|
|
||||||
// outState.putParcelable("historyChart", historyChart.onSaveInstanceState());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnToggleCheckmarkListener(@NonNull OnToggleCheckmarkListener onToggleCheckmarkListener)
|
|
||||||
{
|
|
||||||
this.onToggleCheckmarkListener = onToggleCheckmarkListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHabit(@Nullable Habit habit)
|
|
||||||
{
|
|
||||||
this.habit = habit;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshData()
|
|
||||||
{
|
|
||||||
if (habit == null) return;
|
|
||||||
taskRunner.execute(new RefreshTask());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCommandFinished(@NonNull Command command)
|
|
||||||
{
|
|
||||||
refreshData();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class RefreshTask implements Task
|
|
||||||
{
|
|
||||||
public List<HistoryChart.Square> checkmarks;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doInBackground()
|
|
||||||
{
|
|
||||||
HistoryCardViewModel model = new HistoryCardPresenter().present(
|
|
||||||
habit,
|
|
||||||
prefs.getFirstWeekday(),
|
|
||||||
prefs.isSkipEnabled(),
|
|
||||||
new LightTheme()
|
|
||||||
);
|
|
||||||
checkmarks = model.getSeries();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPostExecute()
|
|
||||||
{
|
|
||||||
if (getContext() == null || habit == null || historyChart == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int color = PaletteUtilsKt.toThemedAndroidColor(habit.getColor(), getContext());
|
|
||||||
// historyChart.setColor(color);
|
|
||||||
// historyChart.setEntries(checkmarks);
|
|
||||||
// historyChart.setNumerical(habit.isNumerical());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* 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.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
|
import org.isoron.platform.gui.AndroidDataView
|
||||||
|
import org.isoron.platform.time.JavaLocalDateFormatter
|
||||||
|
import org.isoron.uhabits.HabitsApplication
|
||||||
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.core.commands.Command
|
||||||
|
import org.isoron.uhabits.core.commands.CommandRunner
|
||||||
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardPresenter
|
||||||
|
import org.isoron.uhabits.core.ui.views.HistoryChart
|
||||||
|
import org.isoron.uhabits.core.ui.views.LightTheme
|
||||||
|
import org.isoron.uhabits.core.ui.views.OnDateClickedListener
|
||||||
|
import org.isoron.uhabits.core.utils.DateUtils
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
|
||||||
|
|
||||||
|
private lateinit var commandRunner: CommandRunner
|
||||||
|
private lateinit var habit: Habit
|
||||||
|
private lateinit var preferences: Preferences
|
||||||
|
private lateinit var dataView: AndroidDataView
|
||||||
|
|
||||||
|
private var chart: HistoryChart? = null
|
||||||
|
private var onDateClickedListener: OnDateClickedListener? = null
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val component = (activity!!.application as HabitsApplication).component
|
||||||
|
commandRunner = component.commandRunner
|
||||||
|
habit = component.habitList.getById(arguments!!.getLong("habit"))!!
|
||||||
|
preferences = component.preferences
|
||||||
|
|
||||||
|
chart = HistoryChart(
|
||||||
|
dateFormatter = JavaLocalDateFormatter(Locale.getDefault()),
|
||||||
|
firstWeekday = preferences.firstWeekday,
|
||||||
|
paletteColor = habit.color,
|
||||||
|
series = emptyList(),
|
||||||
|
theme = LightTheme(),
|
||||||
|
today = DateUtils.getTodayWithOffset().toLocalDate(),
|
||||||
|
onDateClickedListener = onDateClickedListener ?: OnDateClickedListener { },
|
||||||
|
padding = 10.0,
|
||||||
|
)
|
||||||
|
dataView = AndroidDataView(context!!, null)
|
||||||
|
dataView.view = chart!!
|
||||||
|
|
||||||
|
return Dialog(context!!).apply {
|
||||||
|
val metrics = resources.displayMetrics
|
||||||
|
val maxHeight = resources.getDimensionPixelSize(R.dimen.history_editor_max_height)
|
||||||
|
setContentView(dataView)
|
||||||
|
window!!.setLayout(metrics.widthPixels, min(metrics.heightPixels, maxHeight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
commandRunner.addListener(this)
|
||||||
|
refreshData()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
commandRunner.removeListener(this)
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnDateClickedListener(listener: OnDateClickedListener) {
|
||||||
|
onDateClickedListener = listener
|
||||||
|
chart?.onDateClickedListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshData() {
|
||||||
|
val model = HistoryCardPresenter().present(
|
||||||
|
habit,
|
||||||
|
preferences.firstWeekday,
|
||||||
|
preferences.isSkipEnabled,
|
||||||
|
theme = LightTheme()
|
||||||
|
)
|
||||||
|
chart?.series = model.series
|
||||||
|
dataView.postInvalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCommandFinished(command: Command) {
|
||||||
|
refreshData()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ import org.isoron.uhabits.HabitsApplication
|
|||||||
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||||
import org.isoron.uhabits.activities.HabitsDirFinder
|
import org.isoron.uhabits.activities.HabitsDirFinder
|
||||||
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialogFactory
|
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialogFactory
|
||||||
|
import org.isoron.uhabits.activities.common.dialogs.HistoryEditorDialog
|
||||||
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
|
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
|
||||||
import org.isoron.uhabits.core.commands.Command
|
import org.isoron.uhabits.core.commands.Command
|
||||||
import org.isoron.uhabits.core.commands.CommandRunner
|
import org.isoron.uhabits.core.commands.CommandRunner
|
||||||
@@ -51,6 +52,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
private lateinit var habit: Habit
|
private lateinit var habit: Habit
|
||||||
private lateinit var preferences: Preferences
|
private lateinit var preferences: Preferences
|
||||||
private lateinit var themeSwitcher: AndroidThemeSwitcher
|
private lateinit var themeSwitcher: AndroidThemeSwitcher
|
||||||
|
private lateinit var behavior: ShowHabitBehavior
|
||||||
|
|
||||||
private val scope = CoroutineScope(Dispatchers.Main)
|
private val scope = CoroutineScope(Dispatchers.Main)
|
||||||
|
|
||||||
@@ -76,7 +78,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
widgetUpdater = appComponent.widgetUpdater,
|
widgetUpdater = appComponent.widgetUpdater,
|
||||||
)
|
)
|
||||||
|
|
||||||
val behavior = ShowHabitBehavior(
|
behavior = ShowHabitBehavior(
|
||||||
commandRunner = commandRunner,
|
commandRunner = commandRunner,
|
||||||
habit = habit,
|
habit = habit,
|
||||||
habitList = habitList,
|
habitList = habitList,
|
||||||
@@ -118,6 +120,9 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
commandRunner.addListener(this)
|
commandRunner.addListener(this)
|
||||||
|
supportFragmentManager.findFragmentByTag("historyEditor")?.let {
|
||||||
|
(it as HistoryEditorDialog).setOnDateClickedListener(behavior)
|
||||||
|
}
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,16 +19,18 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.activities.habits.show
|
package org.isoron.uhabits.activities.habits.show
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.HapticFeedbackConstants
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialogFactory
|
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialogFactory
|
||||||
import org.isoron.uhabits.activities.common.dialogs.HistoryEditorDialog
|
import org.isoron.uhabits.activities.common.dialogs.HistoryEditorDialog
|
||||||
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
|
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
|
import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
|
||||||
import org.isoron.uhabits.core.ui.callbacks.OnToggleCheckmarkListener
|
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
|
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitBehavior
|
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitBehavior
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuBehavior
|
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuBehavior
|
||||||
|
import org.isoron.uhabits.core.ui.views.OnDateClickedListener
|
||||||
import org.isoron.uhabits.intents.IntentFactory
|
import org.isoron.uhabits.intents.IntentFactory
|
||||||
import org.isoron.uhabits.utils.showMessage
|
import org.isoron.uhabits.utils.showMessage
|
||||||
import org.isoron.uhabits.utils.showSendFileScreen
|
import org.isoron.uhabits.utils.showSendFileScreen
|
||||||
@@ -43,7 +45,11 @@ class ShowHabitScreen(
|
|||||||
val widgetUpdater: WidgetUpdater,
|
val widgetUpdater: WidgetUpdater,
|
||||||
) : ShowHabitBehavior.Screen, ShowHabitMenuBehavior.Screen {
|
) : ShowHabitBehavior.Screen, ShowHabitMenuBehavior.Screen {
|
||||||
|
|
||||||
override fun showNumberPicker(value: Double, unit: String, callback: ListHabitsBehavior.NumberPickerCallback) {
|
override fun showNumberPicker(
|
||||||
|
value: Double,
|
||||||
|
unit: String,
|
||||||
|
callback: ListHabitsBehavior.NumberPickerCallback,
|
||||||
|
) {
|
||||||
numberPickerFactory.create(value, unit, callback).show()
|
numberPickerFactory.create(value, unit, callback).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,13 +61,19 @@ class ShowHabitScreen(
|
|||||||
activity.refresh()
|
activity.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showHistoryEditorDialog(listener: OnToggleCheckmarkListener) {
|
override fun showHistoryEditorDialog(listener: OnDateClickedListener) {
|
||||||
val dialog = HistoryEditorDialog()
|
val dialog = HistoryEditorDialog()
|
||||||
dialog.setHabit(habit)
|
dialog.arguments = Bundle().apply {
|
||||||
dialog.setOnToggleCheckmarkListener(listener)
|
putLong("habit", habit.id!!)
|
||||||
|
}
|
||||||
|
dialog.setOnDateClickedListener(listener)
|
||||||
dialog.show(activity.supportFragmentManager, "historyEditor")
|
dialog.show(activity.supportFragmentManager, "historyEditor")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun touchFeedback() {
|
||||||
|
activity.window.decorView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
override fun showEditHabitScreen(habit: Habit) {
|
override fun showEditHabitScreen(habit: Habit) {
|
||||||
activity.startActivity(intentFactory.startEditActivity(activity, habit))
|
activity.startActivity(intentFactory.startEditActivity(activity, habit))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun update(data: HistoryCardViewModel) {
|
fun update(data: HistoryCardViewModel) {
|
||||||
|
|
||||||
val androidColor = data.color.toThemedAndroidColor(context)
|
val androidColor = data.color.toThemedAndroidColor(context)
|
||||||
binding.title.setTextColor(androidColor)
|
binding.title.setTextColor(androidColor)
|
||||||
binding.chart.view = HistoryChart(
|
binding.chart.view = HistoryChart(
|
||||||
@@ -51,10 +50,6 @@ class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(cont
|
|||||||
series = data.series,
|
series = data.series,
|
||||||
firstWeekday = data.firstWeekday,
|
firstWeekday = data.firstWeekday,
|
||||||
)
|
)
|
||||||
|
binding.chart.postInvalidate()
|
||||||
// binding.historyChart.setSkipEnabled(data.isSkipEnabled)
|
|
||||||
// if (data.isNumerical) {
|
|
||||||
// binding.historyChart.setNumerical(true)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,7 @@
|
|||||||
<dimen name="baseSize">20dp</dimen>
|
<dimen name="baseSize">20dp</dimen>
|
||||||
<dimen name="checkmarkWidth">48dp</dimen>
|
<dimen name="checkmarkWidth">48dp</dimen>
|
||||||
<dimen name="checkmarkHeight">48dp</dimen>
|
<dimen name="checkmarkHeight">48dp</dimen>
|
||||||
<dimen name="history_editor_max_height">450dp</dimen>
|
<dimen name="history_editor_max_height">350dp</dimen>
|
||||||
<dimen name="history_editor_padding">8dp</dimen>
|
|
||||||
<dimen name="regularTextSize">16sp</dimen>
|
<dimen name="regularTextSize">16sp</dimen>
|
||||||
<dimen name="smallTextSize">14sp</dimen>
|
<dimen name="smallTextSize">14sp</dimen>
|
||||||
<dimen name="smallerTextSize">12sp</dimen>
|
<dimen name="smallerTextSize">12sp</dimen>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
BIN
android/uhabits-core/assets/test/views/HistoryChart/weekday.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
@@ -51,6 +51,7 @@ interface Canvas {
|
|||||||
fun fillCircle(centerX: Double, centerY: Double, radius: Double)
|
fun fillCircle(centerX: Double, centerY: Double, radius: Double)
|
||||||
fun setTextAlign(align: TextAlign)
|
fun setTextAlign(align: TextAlign)
|
||||||
fun toImage(): Image
|
fun toImage(): Image
|
||||||
|
fun measureText(test: String): Double
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fills entire canvas with the current color.
|
* Fills entire canvas with the current color.
|
||||||
|
|||||||
@@ -36,13 +36,18 @@ import kotlin.math.roundToInt
|
|||||||
|
|
||||||
class JavaCanvas(
|
class JavaCanvas(
|
||||||
val image: BufferedImage,
|
val image: BufferedImage,
|
||||||
val pixelScale: Double = 2.0
|
val pixelScale: Double = 2.0,
|
||||||
) : Canvas {
|
) : Canvas {
|
||||||
|
|
||||||
override fun toImage(): Image {
|
override fun toImage(): Image {
|
||||||
return JavaImage(image)
|
return JavaImage(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun measureText(text: String): Double {
|
||||||
|
val metrics = g2d.getFontMetrics(g2d.font)
|
||||||
|
return toDp(metrics.stringWidth(text))
|
||||||
|
}
|
||||||
|
|
||||||
private val frc = FontRenderContext(null, true, true)
|
private val frc = FontRenderContext(null, true, true)
|
||||||
private var fontSize = 12.0
|
private var fontSize = 12.0
|
||||||
private var font = Font.REGULAR
|
private var font = Font.REGULAR
|
||||||
@@ -121,7 +126,7 @@ class JavaCanvas(
|
|||||||
y: Double,
|
y: Double,
|
||||||
width: Double,
|
width: Double,
|
||||||
height: Double,
|
height: Double,
|
||||||
cornerRadius: Double
|
cornerRadius: Double,
|
||||||
) {
|
) {
|
||||||
g2d.fill(
|
g2d.fill(
|
||||||
RoundRectangle2D.Double(
|
RoundRectangle2D.Double(
|
||||||
@@ -184,7 +189,7 @@ class JavaCanvas(
|
|||||||
centerY: Double,
|
centerY: Double,
|
||||||
radius: Double,
|
radius: Double,
|
||||||
startAngle: Double,
|
startAngle: Double,
|
||||||
swipeAngle: Double
|
swipeAngle: Double,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
g2d.fillArc(
|
g2d.fillArc(
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ package org.isoron.platform.gui
|
|||||||
|
|
||||||
interface View {
|
interface View {
|
||||||
fun draw(canvas: Canvas)
|
fun draw(canvas: Canvas)
|
||||||
|
fun onClick(x: Double, y: Double) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DataView : View {
|
interface DataView : View {
|
||||||
|
|||||||
@@ -128,9 +128,14 @@ data class LocalDate(val daysSince2000: Int) {
|
|||||||
fun distanceTo(other: LocalDate): Int {
|
fun distanceTo(other: LocalDate): Int {
|
||||||
return abs(daysSince2000 - other.daysSince2000)
|
return abs(daysSince2000 - other.daysSince2000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "LocalDate($year-$month-$day)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LocalDateFormatter {
|
interface LocalDateFormatter {
|
||||||
|
fun shortWeekdayName(weekday: DayOfWeek): String
|
||||||
fun shortWeekdayName(date: LocalDate): String
|
fun shortWeekdayName(date: LocalDate): String
|
||||||
fun shortMonthName(date: LocalDate): String
|
fun shortMonthName(date: LocalDate): String
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,12 @@ class JavaLocalDateFormatter(private val locale: Locale) : LocalDateFormatter {
|
|||||||
return if (longName.length <= 3) longName else shortName
|
return if (longName.length <= 3) longName else shortName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun shortWeekdayName(weekday: DayOfWeek): String {
|
||||||
|
val cal = GregorianCalendar()
|
||||||
|
cal.set(DAY_OF_WEEK, weekday.daysSinceSunday - 1)
|
||||||
|
return shortWeekdayName(LocalDate(cal.get(YEAR), cal.get(MONTH) + 1, cal.get(DAY_OF_MONTH)))
|
||||||
|
}
|
||||||
|
|
||||||
override fun shortWeekdayName(date: LocalDate): String {
|
override fun shortWeekdayName(date: LocalDate): String {
|
||||||
val cal = date.toGregorianCalendar()
|
val cal = date.toGregorianCalendar()
|
||||||
return cal.getDisplayName(DAY_OF_WEEK, SHORT, locale)
|
return cal.getDisplayName(DAY_OF_WEEK, SHORT, locale)
|
||||||
|
|||||||
@@ -18,14 +18,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.isoron.uhabits.core.ui.screens.habits.show
|
package org.isoron.uhabits.core.ui.screens.habits.show
|
||||||
|
|
||||||
|
import org.isoron.platform.time.LocalDate
|
||||||
import org.isoron.uhabits.core.commands.CommandRunner
|
import org.isoron.uhabits.core.commands.CommandRunner
|
||||||
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
|
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
|
||||||
|
import org.isoron.uhabits.core.models.Entry
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
import org.isoron.uhabits.core.models.HabitList
|
import org.isoron.uhabits.core.models.HabitList
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
|
||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.core.ui.callbacks.OnToggleCheckmarkListener
|
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
|
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
|
||||||
|
import org.isoron.uhabits.core.ui.views.OnDateClickedListener
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class ShowHabitBehavior(
|
class ShowHabitBehavior(
|
||||||
@@ -34,7 +35,7 @@ class ShowHabitBehavior(
|
|||||||
private val habit: Habit,
|
private val habit: Habit,
|
||||||
private val screen: Screen,
|
private val screen: Screen,
|
||||||
private val preferences: Preferences,
|
private val preferences: Preferences,
|
||||||
) : OnToggleCheckmarkListener {
|
) : OnDateClickedListener {
|
||||||
|
|
||||||
fun onScoreCardSpinnerPosition(position: Int) {
|
fun onScoreCardSpinnerPosition(position: Int) {
|
||||||
preferences.scoreCardSpinnerPosition = position
|
preferences.scoreCardSpinnerPosition = position
|
||||||
@@ -58,7 +59,9 @@ class ShowHabitBehavior(
|
|||||||
screen.showHistoryEditorDialog(this)
|
screen.showHistoryEditorDialog(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onToggleEntry(timestamp: Timestamp, value: Int) {
|
override fun onDateClicked(date: LocalDate) {
|
||||||
|
val timestamp = date.timestamp
|
||||||
|
screen.touchFeedback()
|
||||||
if (habit.isNumerical) {
|
if (habit.isNumerical) {
|
||||||
val entries = habit.computedEntries
|
val entries = habit.computedEntries
|
||||||
val oldValue = entries.get(timestamp).value
|
val oldValue = entries.get(timestamp).value
|
||||||
@@ -74,12 +77,18 @@ class ShowHabitBehavior(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
val currentValue = habit.computedEntries.get(timestamp).value
|
||||||
|
val nextValue = if (preferences.isSkipEnabled) {
|
||||||
|
Entry.nextToggleValueWithSkip(currentValue)
|
||||||
|
} else {
|
||||||
|
Entry.nextToggleValueWithoutSkip(currentValue)
|
||||||
|
}
|
||||||
commandRunner.run(
|
commandRunner.run(
|
||||||
CreateRepetitionCommand(
|
CreateRepetitionCommand(
|
||||||
habitList,
|
habitList,
|
||||||
habit,
|
habit,
|
||||||
timestamp,
|
timestamp,
|
||||||
value,
|
nextValue,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -89,11 +98,12 @@ class ShowHabitBehavior(
|
|||||||
fun showNumberPicker(
|
fun showNumberPicker(
|
||||||
value: Double,
|
value: Double,
|
||||||
unit: String,
|
unit: String,
|
||||||
callback: ListHabitsBehavior.NumberPickerCallback
|
callback: ListHabitsBehavior.NumberPickerCallback,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun updateWidgets()
|
fun updateWidgets()
|
||||||
fun refresh()
|
fun refresh()
|
||||||
fun showHistoryEditorDialog(listener: OnToggleCheckmarkListener)
|
fun showHistoryEditorDialog(listener: OnDateClickedListener)
|
||||||
|
fun touchFeedback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,15 +28,23 @@ import org.isoron.platform.time.LocalDate
|
|||||||
import org.isoron.platform.time.LocalDateFormatter
|
import org.isoron.platform.time.LocalDateFormatter
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
|
|
||||||
|
fun interface OnDateClickedListener {
|
||||||
|
fun onDateClicked(date: LocalDate)
|
||||||
|
}
|
||||||
|
|
||||||
class HistoryChart(
|
class HistoryChart(
|
||||||
var today: LocalDate,
|
|
||||||
var paletteColor: PaletteColor,
|
|
||||||
var theme: Theme,
|
|
||||||
var dateFormatter: LocalDateFormatter,
|
var dateFormatter: LocalDateFormatter,
|
||||||
var firstWeekday: DayOfWeek,
|
var firstWeekday: DayOfWeek,
|
||||||
|
var paletteColor: PaletteColor,
|
||||||
var series: List<Square>,
|
var series: List<Square>,
|
||||||
|
var theme: Theme,
|
||||||
|
var today: LocalDate,
|
||||||
|
var onDateClickedListener: OnDateClickedListener = OnDateClickedListener { },
|
||||||
|
var padding: Double = 0.0,
|
||||||
) : DataView {
|
) : DataView {
|
||||||
|
|
||||||
enum class Square {
|
enum class Square {
|
||||||
@@ -46,38 +54,58 @@ class HistoryChart(
|
|||||||
HATCHED,
|
HATCHED,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Style
|
|
||||||
var padding = 0.0
|
|
||||||
var squareSpacing = 1.0
|
var squareSpacing = 1.0
|
||||||
override var dataOffset = 0
|
override var dataOffset = 0
|
||||||
private var squareSize = 0.0
|
|
||||||
|
|
||||||
var lastPrintedMonth = ""
|
private var squareSize = 0.0
|
||||||
var lastPrintedYear = ""
|
private var width = 0.0
|
||||||
|
private var height = 0.0
|
||||||
|
private var nColumns = 0
|
||||||
|
private var topLeftOffset = 0
|
||||||
|
private var topLeftDate = LocalDate(2020, 1, 1)
|
||||||
|
private var lastPrintedMonth = ""
|
||||||
|
private var lastPrintedYear = ""
|
||||||
|
private var headerOverflow = 0.0
|
||||||
|
|
||||||
override val dataColumnWidth: Double
|
override val dataColumnWidth: Double
|
||||||
get() = squareSpacing + squareSize
|
get() = squareSpacing + squareSize
|
||||||
|
|
||||||
|
override fun onClick(x: Double, y: Double) {
|
||||||
|
if (width <= 0.0) throw IllegalStateException("onClick must be called after draw(canvas)")
|
||||||
|
val col = ((x - padding) / squareSize).toInt()
|
||||||
|
val row = ((y - padding) / squareSize).toInt()
|
||||||
|
val offset = col * 7 + (row - 1)
|
||||||
|
if (row == 0 || col == nColumns) return
|
||||||
|
val clickedDate = topLeftDate.plus(offset)
|
||||||
|
if (clickedDate.isNewerThan(today)) return
|
||||||
|
onDateClickedListener.onDateClicked(clickedDate)
|
||||||
|
}
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
val width = canvas.getWidth()
|
width = canvas.getWidth()
|
||||||
val height = canvas.getHeight()
|
height = canvas.getHeight()
|
||||||
|
|
||||||
canvas.setColor(theme.cardBackgroundColor)
|
canvas.setColor(theme.cardBackgroundColor)
|
||||||
canvas.fill()
|
canvas.fill()
|
||||||
|
|
||||||
squareSize = round((height - 2 * padding) / 8.0)
|
squareSize = round((height - 2 * padding) / 8.0)
|
||||||
canvas.setFontSize(height * 0.06)
|
canvas.setFontSize(min(14.0, height * 0.06))
|
||||||
|
|
||||||
val nColumns = floor((width - 2 * padding) / squareSize).toInt() - 2
|
val weekdayColumnWidth = DayOfWeek.values().map { weekday ->
|
||||||
|
canvas.measureText(dateFormatter.shortWeekdayName(weekday)) + squareSize * 0.15
|
||||||
|
}.maxOrNull() ?: 0.0
|
||||||
|
|
||||||
|
nColumns = floor((width - 2 * padding - weekdayColumnWidth) / squareSize).toInt()
|
||||||
val firstWeekdayOffset = (
|
val firstWeekdayOffset = (
|
||||||
today.dayOfWeek.daysSinceSunday -
|
today.dayOfWeek.daysSinceSunday -
|
||||||
firstWeekday.daysSinceSunday + 7
|
firstWeekday.daysSinceSunday + 7
|
||||||
) % 7
|
) % 7
|
||||||
val topLeftOffset = (nColumns - 1 + dataOffset) * 7 + firstWeekdayOffset
|
topLeftOffset = (nColumns - 1 + dataOffset) * 7 + firstWeekdayOffset
|
||||||
val topLeftDate = today.minus(topLeftOffset)
|
topLeftDate = today.minus(topLeftOffset)
|
||||||
|
|
||||||
lastPrintedYear = ""
|
lastPrintedYear = ""
|
||||||
lastPrintedMonth = ""
|
lastPrintedMonth = ""
|
||||||
|
headerOverflow = 0.0
|
||||||
|
|
||||||
// Draw main columns
|
// Draw main columns
|
||||||
repeat(nColumns) { column ->
|
repeat(nColumns) { column ->
|
||||||
@@ -93,7 +121,7 @@ class HistoryChart(
|
|||||||
canvas.setTextAlign(TextAlign.LEFT)
|
canvas.setTextAlign(TextAlign.LEFT)
|
||||||
canvas.drawText(
|
canvas.drawText(
|
||||||
dateFormatter.shortWeekdayName(date),
|
dateFormatter.shortWeekdayName(date),
|
||||||
padding + nColumns * squareSize + squareSpacing * 3,
|
padding + nColumns * squareSize + squareSize * 0.15,
|
||||||
padding + squareSize * (row + 1) + squareSize / 2
|
padding + squareSize * (row + 1) + squareSize / 2
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -143,9 +171,12 @@ class HistoryChart(
|
|||||||
canvas.setTextAlign(TextAlign.LEFT)
|
canvas.setTextAlign(TextAlign.LEFT)
|
||||||
canvas.drawText(
|
canvas.drawText(
|
||||||
headerText,
|
headerText,
|
||||||
padding + column * squareSize,
|
headerOverflow + padding + column * squareSize,
|
||||||
padding + squareSize / 2
|
padding + squareSize / 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
headerOverflow += canvas.measureText(headerText) + 0.1 * squareSize
|
||||||
|
headerOverflow = max(0.0, headerOverflow - squareSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun drawSquare(
|
private fun drawSquare(
|
||||||
|
|||||||
@@ -33,19 +33,27 @@ import org.isoron.uhabits.core.ui.views.HistoryChart.Square.HATCHED
|
|||||||
import org.isoron.uhabits.core.ui.views.HistoryChart.Square.OFF
|
import org.isoron.uhabits.core.ui.views.HistoryChart.Square.OFF
|
||||||
import org.isoron.uhabits.core.ui.views.HistoryChart.Square.ON
|
import org.isoron.uhabits.core.ui.views.HistoryChart.Square.ON
|
||||||
import org.isoron.uhabits.core.ui.views.LightTheme
|
import org.isoron.uhabits.core.ui.views.LightTheme
|
||||||
|
import org.isoron.uhabits.core.ui.views.OnDateClickedListener
|
||||||
import org.isoron.uhabits.core.ui.views.WidgetTheme
|
import org.isoron.uhabits.core.ui.views.WidgetTheme
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.mockito.Mockito.mock
|
||||||
|
import org.mockito.Mockito.reset
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.Mockito.verifyNoMoreInteractions
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class HistoryChartTest {
|
class HistoryChartTest {
|
||||||
val base = "views/HistoryChart"
|
val base = "views/HistoryChart"
|
||||||
|
|
||||||
|
val dateClickedListener = mock(OnDateClickedListener::class.java)
|
||||||
|
|
||||||
val view = HistoryChart(
|
val view = HistoryChart(
|
||||||
today = LocalDate(2015, 1, 25),
|
today = LocalDate(2015, 1, 25),
|
||||||
paletteColor = PaletteColor(7),
|
paletteColor = PaletteColor(7),
|
||||||
theme = LightTheme(),
|
theme = LightTheme(),
|
||||||
dateFormatter = JavaLocalDateFormatter(Locale.US),
|
dateFormatter = JavaLocalDateFormatter(Locale.US),
|
||||||
firstWeekday = SUNDAY,
|
firstWeekday = SUNDAY,
|
||||||
|
onDateClickedListener = dateClickedListener,
|
||||||
series = listOf(
|
series = listOf(
|
||||||
2, // today
|
2, // today
|
||||||
2, 1, 2, 1, 2, 1, 2,
|
2, 1, 2, 1, 2, 1, 2,
|
||||||
@@ -71,16 +79,42 @@ class HistoryChartTest {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Label overflow
|
|
||||||
// TODO: onClick
|
|
||||||
// TODO: HistoryEditorDialog
|
|
||||||
// TODO: Remove excessive padding on widgets
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDraw() = runBlocking {
|
fun testDraw() = runBlocking {
|
||||||
assertRenders(400, 200, "$base/base.png", view)
|
assertRenders(400, 200, "$base/base.png", view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testClick() = runBlocking {
|
||||||
|
assertRenders(400, 200, "$base/base.png", view)
|
||||||
|
|
||||||
|
// Click top left date
|
||||||
|
view.onClick(20.0, 46.0)
|
||||||
|
verify(dateClickedListener).onDateClicked(LocalDate(2014, 10, 26))
|
||||||
|
reset(dateClickedListener)
|
||||||
|
view.onClick(2.0, 28.0)
|
||||||
|
verify(dateClickedListener).onDateClicked(LocalDate(2014, 10, 26))
|
||||||
|
reset(dateClickedListener)
|
||||||
|
|
||||||
|
// Click date in the middle
|
||||||
|
view.onClick(163.0, 113.0)
|
||||||
|
verify(dateClickedListener).onDateClicked(LocalDate(2014, 12, 10))
|
||||||
|
reset(dateClickedListener)
|
||||||
|
|
||||||
|
// Click today
|
||||||
|
view.onClick(336.0, 37.0)
|
||||||
|
verify(dateClickedListener).onDateClicked(LocalDate(2015, 1, 25))
|
||||||
|
reset(dateClickedListener)
|
||||||
|
|
||||||
|
// Click header
|
||||||
|
view.onClick(160.0, 15.0)
|
||||||
|
verifyNoMoreInteractions(dateClickedListener)
|
||||||
|
|
||||||
|
// Click right axis
|
||||||
|
view.onClick(360.0, 60.0)
|
||||||
|
verifyNoMoreInteractions(dateClickedListener)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDrawWeekDay() = runBlocking {
|
fun testDrawWeekDay() = runBlocking {
|
||||||
view.firstWeekday = DayOfWeek.MONDAY
|
view.firstWeekday = DayOfWeek.MONDAY
|
||||||
|
|||||||