ShowHabitActivity: Re-enable menus and actions

pull/699/head
Alinson S. Xavier 5 years ago
parent 85de69bca7
commit 19e221bb32

@ -24,6 +24,7 @@ import androidx.test.filters.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.ui.callbacks.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
@ -43,7 +44,7 @@ public class HistoryChartTest extends BaseViewTest
Timestamp today;
private HistoryChart.Controller controller;
private OnToggleCheckmarkListener onToggleCheckmarkListener;
@Override
@Before
@ -61,8 +62,8 @@ public class HistoryChartTest extends BaseViewTest
chart.setColor(PaletteUtilsKt.toFixedAndroidColor(habit.getColor()));
measureView(chart, dpToPixels(400), dpToPixels(200));
controller = mock(HistoryChart.Controller.class);
chart.setController(controller);
onToggleCheckmarkListener = mock(OnToggleCheckmarkListener.class);
chart.setOnToggleCheckmarkListener(onToggleCheckmarkListener);
}
@Test
@ -72,21 +73,16 @@ public class HistoryChartTest extends BaseViewTest
chart.tap(dpToPixels(118), dpToPixels(13)); // header
chart.tap(dpToPixels(336), dpToPixels(60)); // tomorrow's square
chart.tap(dpToPixels(370), dpToPixels(60)); // right axis
verifyNoMoreInteractions(controller);
verifyNoMoreInteractions(onToggleCheckmarkListener);
}
@Test
public void tapDate_withEditableView() throws Throwable
{
chart.setIsEditable(true);
chart.tap(dpToPixels(340), dpToPixels(40));
verify(controller).onToggleCheckmark(today, Checkmark.SKIP);
chart.tap(dpToPixels(340), dpToPixels(40));
verify(controller).onToggleCheckmark(today, Checkmark.NO);
chart.tap(dpToPixels(340), dpToPixels(40));
verify(controller).onToggleCheckmark(today, Checkmark.YES_MANUAL);
verifyNoMoreInteractions(controller);
verify(onToggleCheckmarkListener).onToggleCheckmark(today, Checkmark.SKIP);
verifyNoMoreInteractions(onToggleCheckmarkListener);
}
@Test
@ -95,8 +91,8 @@ public class HistoryChartTest extends BaseViewTest
chart.setIsEditable(true);
chart.setCheckmarks(new int[]{});
chart.tap(dpToPixels(340), dpToPixels(40));
verify(controller).onToggleCheckmark(today, Checkmark.YES_MANUAL);
verifyNoMoreInteractions(controller);
verify(onToggleCheckmarkListener).onToggleCheckmark(today, Checkmark.YES_MANUAL);
verifyNoMoreInteractions(onToggleCheckmarkListener);
}
@Test
@ -104,7 +100,7 @@ public class HistoryChartTest extends BaseViewTest
{
chart.setIsEditable(false);
chart.tap(dpToPixels(340), dpToPixels(40));
verifyNoMoreInteractions(controller);
verifyNoMoreInteractions(onToggleCheckmarkListener);
}
@Test

@ -34,7 +34,9 @@ import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.tasks.*;
import org.isoron.uhabits.core.ui.callbacks.*;
import org.isoron.uhabits.utils.*;
import org.jetbrains.annotations.*;
import static org.isoron.androidbase.utils.InterfaceUtils.*;
@ -48,7 +50,7 @@ public class HistoryEditorDialog extends AppCompatDialogFragment
HistoryChart historyChart;
@NonNull
private Controller controller;
private OnToggleCheckmarkListener onToggleCheckmarkListener;
private HabitList habitList;
@ -58,7 +60,13 @@ public class HistoryEditorDialog extends AppCompatDialogFragment
public HistoryEditorDialog()
{
this.controller = new Controller() {};
this.onToggleCheckmarkListener = new OnToggleCheckmarkListener()
{
@Override
public void onToggleCheckmark(@NotNull Timestamp timestamp, int value)
{
}
};
}
@Override
@ -80,7 +88,7 @@ public class HistoryEditorDialog extends AppCompatDialogFragment
prefs = app.getComponent().getPreferences();
historyChart = new HistoryChart(context);
historyChart.setController(controller);
historyChart.setOnToggleCheckmarkListener(onToggleCheckmarkListener);
historyChart.setFirstWeekday(prefs.getFirstWeekday());
historyChart.setSkipEnabled(prefs.isSkipEnabled());
@ -144,10 +152,9 @@ public class HistoryEditorDialog extends AppCompatDialogFragment
outState.putParcelable("historyChart", historyChart.onSaveInstanceState());
}
public void setController(@NonNull Controller controller)
public void setOnToggleCheckmarkListener(@NonNull OnToggleCheckmarkListener onToggleCheckmarkListener)
{
this.controller = controller;
if (historyChart != null) historyChart.setController(controller);
this.onToggleCheckmarkListener = onToggleCheckmarkListener;
}
public void setHabit(@Nullable Habit habit)
@ -161,8 +168,6 @@ public class HistoryEditorDialog extends AppCompatDialogFragment
taskRunner.execute(new RefreshTask());
}
public interface Controller extends HistoryChart.Controller {}
private class RefreshTask implements Task
{
public int[] checkmarks;

@ -27,12 +27,15 @@ import android.util.*;
import android.view.*;
import androidx.annotation.*;
import androidx.annotation.Nullable;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.ui.callbacks.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.utils.*;
import org.jetbrains.annotations.*;
import java.text.*;
import java.util.*;
@ -100,7 +103,7 @@ public class HistoryChart extends ScrollableChart
private int firstWeekday = Calendar.SUNDAY;
@NonNull
private Controller controller;
private OnToggleCheckmarkListener onToggleCheckmarkListener;
private boolean skipsEnabled;
@ -159,10 +162,9 @@ public class HistoryChart extends ScrollableChart
newValue = Repetition.nextToggleValueWithSkip(checkmarks[offset]);
else
newValue = Repetition.nextToggleValueWithoutSkip(checkmarks[offset]);
checkmarks[offset] = newValue;
}
controller.onToggleCheckmark(timestamp, newValue);
onToggleCheckmarkListener.onToggleCheckmark(timestamp, newValue);
postInvalidate();
return true;
@ -199,9 +201,9 @@ public class HistoryChart extends ScrollableChart
postInvalidate();
}
public void setController(@NonNull Controller controller)
public void setOnToggleCheckmarkListener(@NonNull OnToggleCheckmarkListener onToggleCheckmarkListener)
{
this.controller = controller;
this.onToggleCheckmarkListener = onToggleCheckmarkListener;
}
public void setNumerical(boolean numerical)
@ -450,7 +452,13 @@ public class HistoryChart extends ScrollableChart
{
isEditable = false;
checkmarks = new int[0];
controller = new Controller() {};
onToggleCheckmarkListener = new OnToggleCheckmarkListener()
{
@Override
public void onToggleCheckmark(@NotNull Timestamp timestamp, int value)
{
}
};
target = 2;
initColors();
@ -552,9 +560,4 @@ public class HistoryChart extends ScrollableChart
baseDate.add(Calendar.DAY_OF_YEAR, -nDays);
baseDate.add(Calendar.DAY_OF_YEAR, -todayPositionInColumn);
}
public interface Controller
{
default void onToggleCheckmark(Timestamp timestamp, int value) {}
}
}

@ -21,85 +21,94 @@ package org.isoron.uhabits.activities.habits.show
import android.content.*
import android.os.*
import android.view.*
import android.widget.*
import androidx.appcompat.app.*
import kotlinx.coroutines.*
import org.isoron.androidbase.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.*
import org.isoron.uhabits.activities.common.dialogs.*
import org.isoron.uhabits.activities.habits.show.views.*
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.widgets.*
import org.isoron.uhabits.core.ui.screens.habits.show.*
import org.isoron.uhabits.intents.*
class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
private lateinit var habit: Habit
private lateinit var commandRunner: CommandRunner
private lateinit var preferences: Preferences
private lateinit var menu: ShowHabitMenu
private lateinit var presenter: ShowHabitPresenter
private lateinit var view: ShowHabitView
private lateinit var widgetUpdater: WidgetUpdater
private val scope = CoroutineScope(Dispatchers.Main)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val appComponent = (applicationContext as HabitsApplication).component
val habitList = appComponent.habitList
habit = habitList.getById(ContentUris.parseId(intent.data!!))!!
preferences = appComponent.preferences
val habit = habitList.getById(ContentUris.parseId(intent.data!!))!!
val preferences = appComponent.preferences
commandRunner = appComponent.commandRunner
widgetUpdater = appComponent.widgetUpdater
AndroidThemeSwitcher(this, preferences).apply()
view = ShowHabitView(this)
presenter = ShowHabitPresenter(
habit = habit,
context = this,
preferences = appComponent.preferences
habit = habit,
preferences = appComponent.preferences,
)
view.onScoreCardSpinnerPosition = { position ->
preferences.scoreCardSpinnerPosition = position
updateWidgets()
updateViews()
}
val screen = ShowHabitScreen(
activity = this,
confirmDeleteDialogFactory = ConfirmDeleteDialogFactory { this },
habit = habit,
intentFactory = IntentFactory(),
numberPickerFactory = NumberPickerFactory(this),
widgetUpdater = appComponent.widgetUpdater,
)
view.onBarCardBoolSpinnerPosition = { position ->
preferences.barCardBoolSpinnerPosition = position
updateViews()
updateWidgets()
}
val behavior = ShowHabitBehavior(
commandRunner = commandRunner,
habit = habit,
habitList = habitList,
preferences = preferences,
screen = screen,
)
view.onBarCardNumericalSpinnerPosition = { position ->
preferences.barCardNumericalSpinnerPosition = position
updateViews()
updateWidgets()
}
val menuBehavior = ShowHabitMenuBehavior(
commandRunner = commandRunner,
habit = habit,
habitList = habitList,
screen = screen,
system = HabitsDirFinder(AndroidDirFinder(this)),
taskRunner = appComponent.taskRunner,
)
view.onClickEditHistoryButton = {
val dialog = HistoryEditorDialog()
dialog.setHabit(habit)
dialog.setController(object : HistoryEditorDialog.Controller {
})
dialog.show(getSupportFragmentManager(), "historyEditor")
}
menu = ShowHabitMenu(
activity = this,
behavior = menuBehavior,
preferences = preferences,
)
view.onScoreCardSpinnerPosition = behavior::onScoreCardSpinnerPosition
view.onBarCardBoolSpinnerPosition = behavior::onBarCardBoolSpinnerPosition
view.onBarCardNumericalSpinnerPosition = behavior::onBarCardNumericalSpinnerPosition
view.onClickEditHistoryButton = behavior::onClickEditHistory
setContentView(view)
}
private fun updateWidgets() {
widgetUpdater.updateWidgets(habit.id)
override fun onCreateOptionsMenu(m: Menu): Boolean {
return menu.onCreateOptionsMenu(m)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return menu.onOptionsItemSelected(item)
}
override fun onResume() {
super.onResume()
commandRunner.addListener(this)
updateViews()
refresh()
}
override fun onPause() {
@ -108,108 +117,13 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
}
override fun onCommandExecuted(command: Command?, refreshKey: Long?) {
updateViews()
refresh()
}
private fun updateViews() {
fun refresh() {
scope.launch {
view.update(presenter.present())
}
}
}
data class ShowHabitViewModel(
val title: String = "",
val isNumerical: Boolean = false,
val color: PaletteColor = PaletteColor(1),
val subtitle: SubtitleCardViewModel,
val overview: OverviewCardViewModel,
val notes: NotesCardViewModel,
val target: TargetCardViewModel,
val streaks: StreakCardViewModel,
val scores: ScoreCardViewModel,
val frequency: FrequencyCardViewModel,
val history: HistoryCardViewModel,
val bar: BarCardViewModel,
)
class ShowHabitView(context: Context) : FrameLayout(context) {
private val binding = ShowHabitBinding.inflate(LayoutInflater.from(context))
var onScoreCardSpinnerPosition: (position: Int) -> Unit = {}
var onClickEditHistoryButton: () -> Unit = {}
var onBarCardBoolSpinnerPosition: (position: Int) -> Unit = {}
var onBarCardNumericalSpinnerPosition: (position: Int) -> Unit = {}
init {
addView(binding.root)
binding.scoreCard.onSpinnerPosition = { onScoreCardSpinnerPosition(it) }
binding.historyCard.onClickEditButton = { onClickEditHistoryButton() }
binding.barCard.onBoolSpinnerPosition = { onBarCardBoolSpinnerPosition(it) }
binding.barCard.onNumericalSpinnerPosition = { onBarCardNumericalSpinnerPosition(it) }
}
fun update(data: ShowHabitViewModel) {
setupToolbar(binding.toolbar, title = data.title, color = data.color)
binding.subtitleCard.update(data.subtitle)
binding.overviewCard.update(data.overview)
binding.notesCard.update(data.notes)
binding.targetCard.update(data.target)
binding.streakCard.update(data.streaks)
binding.scoreCard.update(data.scores)
binding.frequencyCard.update(data.frequency)
binding.historyCard.update(data.history)
binding.barCard.update(data.bar)
if (data.isNumerical) {
binding.overviewCard.visibility = GONE
binding.streakCard.visibility = GONE
} else {
binding.targetCard.visibility = GONE
}
}
}
class ShowHabitPresenter(
val habit: Habit,
val context: Context,
val preferences: Preferences,
) {
private val subtitleCardPresenter = SubtitleCardPresenter(habit, context)
private val overviewCardPresenter = OverviewCardPresenter(habit)
private val notesCardPresenter = NotesCardPresenter(habit)
private val targetCardPresenter = TargetCardPresenter(habit = habit,
firstWeekday = preferences.firstWeekday,
resources = context.resources)
private val streakCartPresenter = StreakCartPresenter(habit)
private val scoreCardPresenter = ScoreCardPresenter(habit = habit,
firstWeekday = preferences.firstWeekday)
private val frequencyCardPresenter = FrequencyCardPresenter(habit = habit,
firstWeekday = preferences.firstWeekday)
private val historyCardViewModel = HistoryCardPresenter(habit = habit,
firstWeekday = preferences.firstWeekday,
isSkipEnabled = preferences.isSkipEnabled)
private val barCardPresenter = BarCardPresenter(habit = habit,
firstWeekday = preferences.firstWeekday)
suspend fun present(): ShowHabitViewModel {
return ShowHabitViewModel(
title = habit.name,
color = habit.color,
isNumerical = habit.isNumerical,
subtitle = subtitleCardPresenter.present(),
overview = overviewCardPresenter.present(),
notes = notesCardPresenter.present(),
target = targetCardPresenter.present(),
streaks = streakCartPresenter.present(),
scores = scoreCardPresenter.present(
spinnerPosition = preferences.scoreCardSpinnerPosition
),
frequency = frequencyCardPresenter.present(),
history = historyCardViewModel.present(),
bar = barCardPresenter.present(
boolSpinnerPosition = preferences.barCardBoolSpinnerPosition,
numericalSpinnerPosition = preferences.barCardNumericalSpinnerPosition,
),
)
}
}

@ -0,0 +1,61 @@
/*
* 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.show
import android.view.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.screens.habits.show.*
class ShowHabitMenu(
val activity: ShowHabitActivity,
val behavior: ShowHabitMenuBehavior,
val preferences: Preferences,
) {
fun onCreateOptionsMenu(menu: Menu): Boolean {
activity.menuInflater.inflate(R.menu.show_habit, menu)
if (preferences.isDeveloper) {
menu.findItem(R.id.action_randomize).isVisible = true
}
return true
}
fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId) {
R.id.action_edit_habit -> {
behavior.onEditHabit()
return true
}
R.id.action_delete -> {
behavior.onDeleteHabit()
return true
}
R.id.action_randomize -> {
behavior.onRandomize()
return true
}
R.id.export -> {
behavior.onExportCSV()
return true
}
}
return false
}
}

@ -1,156 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.habits.show;
import android.content.*;
import androidx.annotation.NonNull;
import org.isoron.androidbase.activities.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.common.dialogs.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.ui.callbacks.*;
import org.isoron.uhabits.core.ui.screens.habits.list.*;
import org.isoron.uhabits.core.ui.screens.habits.show.*;
import org.isoron.uhabits.intents.*;
import javax.inject.*;
import dagger.*;
@ActivityScope
public class ShowHabitScreen extends BaseScreen
implements ShowHabitMenuBehavior.Screen,
ShowHabitBehavior.Screen,
HistoryEditorDialog.Controller
//ShowHabitRootView.Controller
{
@NonNull
private final Habit habit;
@NonNull
private final ConfirmDeleteDialogFactory confirmDeleteDialogFactory;
private final Lazy<ShowHabitBehavior> behavior;
@NonNull
private final IntentFactory intentFactory;
@NonNull
private final NumberPickerFactory numberPickerFactory;
@Inject
public ShowHabitScreen(@NonNull BaseActivity activity,
@NonNull Habit habit,
//@NonNull ShowHabitRootView view,
@NonNull ShowHabitsMenu menu,
@NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory,
@NonNull IntentFactory intentFactory,
@NonNull NumberPickerFactory numberPickerFactory,
@NonNull Lazy<ShowHabitBehavior> behavior)
{
super(activity);
this.intentFactory = intentFactory;
setMenu(menu);
//setRootView(view);
this.habit = habit;
this.behavior = behavior;
this.confirmDeleteDialogFactory = confirmDeleteDialogFactory;
this.numberPickerFactory = numberPickerFactory;
//view.setController(this);
}
// @Override
// public void onEditHistoryButtonClick()
// {
// behavior.get().onEditHistory();
// }
@Override
public void showNumberPicker(double value,
@NonNull String unit,
@NonNull ListHabitsBehavior.NumberPickerCallback callback)
{
numberPickerFactory.create(value, unit, callback).show();
}
@Override
public void onToggleCheckmark(Timestamp timestamp, int value)
{
behavior.get().onToggleCheckmark(timestamp, value);
}
// @Override
// public void onToolbarChanged()
// {
// invalidateToolbar();
// }
@Override
public void reattachDialogs()
{
super.reattachDialogs();
HistoryEditorDialog historyEditor = (HistoryEditorDialog) activity
.getSupportFragmentManager()
.findFragmentByTag("historyEditor");
if (historyEditor != null) historyEditor.setController(this);
}
@Override
public void showEditHabitScreen(@NonNull Habit habit)
{
Intent intent = intentFactory.startEditActivity(activity, habit);
activity.startActivity(intent);
}
@Override
public void showEditHistoryScreen()
{
HistoryEditorDialog dialog = new HistoryEditorDialog();
dialog.setHabit(habit);
dialog.setController(this);
dialog.show(activity.getSupportFragmentManager(), "historyEditor");
}
@Override
public void showMessage(ShowHabitMenuBehavior.Message m)
{
switch (m)
{
case COULD_NOT_EXPORT:
showMessage(R.string.could_not_export);
case HABIT_DELETED:
showMessage(R.string.delete_habits_message);
}
}
@Override
public void showDeleteConfirmationScreen(@NonNull OnConfirmedCallback callback) {
activity.showDialog(confirmDeleteDialogFactory.create(callback));
}
@Override
public void close() {
activity.finish();
}
}

@ -0,0 +1,83 @@
/*
* 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.show
import org.isoron.uhabits.activities.common.dialogs.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.ui.callbacks.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.core.ui.screens.habits.show.*
import org.isoron.uhabits.intents.*
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.widgets.*
class ShowHabitScreen(
val activity: ShowHabitActivity,
val confirmDeleteDialogFactory: ConfirmDeleteDialogFactory,
val habit: Habit,
val intentFactory: IntentFactory,
val numberPickerFactory: NumberPickerFactory,
val widgetUpdater: WidgetUpdater,
) : ShowHabitBehavior.Screen, ShowHabitMenuBehavior.Screen {
override fun showNumberPicker(value: Double, unit: String, callback: ListHabitsBehavior.NumberPickerCallback) {
numberPickerFactory.create(value, unit, callback).show();
}
override fun updateWidgets() {
widgetUpdater.updateWidgets(habit.id)
}
override fun refresh() {
activity.refresh()
}
override fun showHistoryEditorDialog(listener: OnToggleCheckmarkListener) {
val dialog = HistoryEditorDialog()
dialog.setHabit(habit)
dialog.setOnToggleCheckmarkListener(listener)
dialog.show(activity.supportFragmentManager, "historyEditor")
}
override fun showEditHabitScreen(habit: Habit) {
activity.startActivity(intentFactory.startEditActivity(activity, habit))
}
override fun showMessage(m: ShowHabitMenuBehavior.Message?) {
when (m) {
ShowHabitMenuBehavior.Message.COULD_NOT_EXPORT -> {
activity.showMessage(org.isoron.uhabits.R.string.could_not_export)
}
}
}
override fun showSendFileScreen(filename: String) {
activity.showSendFileScreen(filename)
}
override fun showDeleteConfirmationScreen(callback: OnConfirmedCallback) {
confirmDeleteDialogFactory.create(callback).show()
}
override fun close() {
activity.finish()
}
}

@ -0,0 +1,135 @@
/*
* 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.show
import android.content.*
import android.view.*
import android.widget.*
import org.isoron.uhabits.activities.habits.show.views.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
data class ShowHabitViewModel(
val title: String = "",
val isNumerical: Boolean = false,
val color: PaletteColor = PaletteColor(1),
val subtitle: SubtitleCardViewModel,
val overview: OverviewCardViewModel,
val notes: NotesCardViewModel,
val target: TargetCardViewModel,
val streaks: StreakCardViewModel,
val scores: ScoreCardViewModel,
val frequency: FrequencyCardViewModel,
val history: HistoryCardViewModel,
val bar: BarCardViewModel,
)
class ShowHabitView(context: Context) : FrameLayout(context) {
private val binding = ShowHabitBinding.inflate(LayoutInflater.from(context))
var onScoreCardSpinnerPosition: (position: Int) -> Unit = {}
var onClickEditHistoryButton: () -> Unit = {}
var onBarCardBoolSpinnerPosition: (position: Int) -> Unit = {}
var onBarCardNumericalSpinnerPosition: (position: Int) -> Unit = {}
init {
addView(binding.root)
binding.scoreCard.onSpinnerPosition = { onScoreCardSpinnerPosition(it) }
binding.historyCard.onClickEditButton = { onClickEditHistoryButton() }
binding.barCard.onBoolSpinnerPosition = { onBarCardBoolSpinnerPosition(it) }
binding.barCard.onNumericalSpinnerPosition = { onBarCardNumericalSpinnerPosition(it) }
}
fun update(data: ShowHabitViewModel) {
setupToolbar(binding.toolbar, title = data.title, color = data.color)
binding.subtitleCard.update(data.subtitle)
binding.overviewCard.update(data.overview)
binding.notesCard.update(data.notes)
binding.targetCard.update(data.target)
binding.streakCard.update(data.streaks)
binding.scoreCard.update(data.scores)
binding.frequencyCard.update(data.frequency)
binding.historyCard.update(data.history)
binding.barCard.update(data.bar)
if (data.isNumerical) {
binding.overviewCard.visibility = GONE
binding.streakCard.visibility = GONE
} else {
binding.targetCard.visibility = GONE
}
}
}
class ShowHabitPresenter(
val habit: Habit,
val context: Context,
val preferences: Preferences,
) {
private val subtitleCardPresenter = SubtitleCardPresenter(habit, context)
private val overviewCardPresenter = OverviewCardPresenter(habit)
private val notesCardPresenter = NotesCardPresenter(habit)
private val targetCardPresenter = TargetCardPresenter(
habit = habit,
firstWeekday = preferences.firstWeekday,
resources = context.resources,
)
private val streakCartPresenter = StreakCartPresenter(habit)
private val scoreCardPresenter = ScoreCardPresenter(
habit = habit,
firstWeekday = preferences.firstWeekday,
)
private val frequencyCardPresenter = FrequencyCardPresenter(
habit = habit,
firstWeekday = preferences.firstWeekday,
)
private val historyCardViewModel = HistoryCardPresenter(
habit = habit,
firstWeekday = preferences.firstWeekday,
isSkipEnabled = preferences.isSkipEnabled,
)
private val barCardPresenter = BarCardPresenter(
habit = habit,
firstWeekday = preferences.firstWeekday,
)
suspend fun present(): ShowHabitViewModel {
return ShowHabitViewModel(
title = habit.name,
color = habit.color,
isNumerical = habit.isNumerical,
subtitle = subtitleCardPresenter.present(),
overview = overviewCardPresenter.present(),
notes = notesCardPresenter.present(),
target = targetCardPresenter.present(),
streaks = streakCartPresenter.present(),
scores = scoreCardPresenter.present(
spinnerPosition = preferences.scoreCardSpinnerPosition
),
frequency = frequencyCardPresenter.present(),
history = historyCardViewModel.present(),
bar = barCardPresenter.present(
boolSpinnerPosition = preferences.barCardBoolSpinnerPosition,
numericalSpinnerPosition = preferences.barCardNumericalSpinnerPosition,
),
)
}
}

@ -1,94 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.habits.show;
import android.view.*;
import androidx.annotation.NonNull;
import org.isoron.androidbase.activities.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.preferences.Preferences;
import org.isoron.uhabits.core.ui.screens.habits.show.*;
import javax.inject.*;
import dagger.*;
@ActivityScope
public class ShowHabitsMenu extends BaseMenu
{
// @NonNull
// private Lazy<ShowHabitMenuBehavior> behavior;
@NonNull
private final Preferences prefs;
@Inject
public ShowHabitsMenu(@NonNull BaseActivity activity,
//@NonNull Lazy<ShowHabitMenuBehavior> behavior,
@NonNull Preferences prefs)
{
super(activity);
//this.behavior = behavior;
this.prefs = prefs;
}
@Override
public void onCreate(@NonNull Menu menu)
{
super.onCreate(menu);
if (prefs.isDeveloper())
menu.findItem(R.id.action_randomize).setVisible(true);
}
@Override
public boolean onItemSelected(@NonNull MenuItem item)
{
switch (item.getItemId())
{
// case R.id.action_edit_habit:
// behavior.get().onEditHabit();
// return true;
//
// case R.id.export:
// behavior.get().onExportCSV();
// return true;
//
// case R.id.action_delete:
// behavior.get().onDeleteHabit();
// return true;
//
// case R.id.action_randomize:
// behavior.get().onRandomize();
// return true;
default:
return false;
}
}
@Override
protected int getMenuResourceId()
{
return R.menu.show_habit;
}
}

@ -19,6 +19,8 @@
package org.isoron.uhabits.utils
import android.app.*
import android.content.*
import android.graphics.*
import android.graphics.drawable.*
import android.view.*
@ -28,10 +30,12 @@ import android.widget.RelativeLayout.*
import androidx.annotation.*
import androidx.appcompat.app.*
import androidx.appcompat.widget.Toolbar
import androidx.core.content.*
import com.google.android.material.snackbar.*
import org.isoron.androidbase.utils.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import java.io.*
fun RelativeLayout.addBelow(view: View,
subject: View,
@ -86,6 +90,21 @@ fun View.showMessage(@StringRes stringId: Int) {
}
}
fun Activity.showMessage(@StringRes stringId: Int) {
this.findViewById<View>(android.R.id.content).showMessage(stringId)
}
fun Activity.showSendFileScreen(archiveFilename: String) {
val file = File(archiveFilename)
val fileUri = FileProvider.getUriForFile(this, "org.isoron.uhabits", file)
this.startActivity(Intent().apply {
action = Intent.ACTION_SEND
type = "application/zip"
putExtra(Intent.EXTRA_STREAM, fileUri)
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
})
}
fun View.setupToolbar(toolbar: Toolbar, title: String, color: PaletteColor) {
toolbar.elevation = InterfaceUtils.dpToPixels(context, 2f)
val res = StyledResources(context)

@ -0,0 +1,26 @@
/*
* 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.core.ui.callbacks
import org.isoron.uhabits.core.models.*
interface OnToggleCheckmarkListener {
fun onToggleCheckmark(timestamp: Timestamp, value: Int) {}
}

@ -1,87 +0,0 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.ui.screens.habits.show;
import androidx.annotation.*;
import org.isoron.uhabits.core.commands.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.ui.screens.habits.list.*;
import javax.inject.*;
public class ShowHabitBehavior
{
private HabitList habitList;
@NonNull
private final Habit habit;
@NonNull
private final CommandRunner commandRunner;
@NonNull
private Screen screen;
@Inject
public ShowHabitBehavior(@NonNull HabitList habitList,
@NonNull CommandRunner commandRunner,
@NonNull Habit habit,
@NonNull Screen screen)
{
this.habitList = habitList;
this.habit = habit;
this.commandRunner = commandRunner;
this.screen = screen;
}
public void onEditHistory()
{
screen.showEditHistoryScreen();
}
public void onToggleCheckmark(Timestamp timestamp, int value)
{
if (habit.isNumerical()) {
CheckmarkList checkmarks = habit.getCheckmarks();
double oldValue = checkmarks.getValues(timestamp, timestamp)[0];
screen.showNumberPicker(oldValue / 1000, habit.getUnit(), newValue ->
{
newValue = Math.round(newValue * 1000);
commandRunner.execute(
new CreateRepetitionCommand(habitList, habit, timestamp, (int) newValue),
habit.getId());
});
} else {
commandRunner.execute(
new CreateRepetitionCommand(habitList, habit, timestamp, value), null);
}
}
public interface Screen
{
void showEditHistoryScreen();
void showNumberPicker(double value,
@NonNull String unit,
@NonNull ListHabitsBehavior.NumberPickerCallback callback);
}
}

@ -0,0 +1,95 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.ui.screens.habits.show
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.callbacks.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
class ShowHabitBehavior(
private val habitList: HabitList,
private val commandRunner: CommandRunner,
private val habit: Habit,
private val screen: Screen,
private val preferences: Preferences,
) : OnToggleCheckmarkListener {
fun onScoreCardSpinnerPosition(position: Int) {
preferences.scoreCardSpinnerPosition = position
screen.updateWidgets()
screen.refresh()
}
fun onBarCardBoolSpinnerPosition(position: Int) {
preferences.barCardBoolSpinnerPosition = position
screen.updateWidgets()
screen.refresh()
}
fun onBarCardNumericalSpinnerPosition(position: Int) {
preferences.barCardNumericalSpinnerPosition = position
screen.refresh()
screen.updateWidgets()
}
fun onClickEditHistory() {
screen.showHistoryEditorDialog(this)
}
override fun onToggleCheckmark(timestamp: Timestamp, value: Int) {
if (habit.isNumerical) {
val checkmarks = habit.checkmarks
val oldValue = checkmarks.getValues(timestamp, timestamp)[0].toDouble()
screen.showNumberPicker(oldValue / 1000, habit.unit) { newValue: Double ->
val thousands = Math.round(newValue * 1000).toInt()
commandRunner.execute(
CreateRepetitionCommand(
habitList,
habit,
timestamp,
thousands,
),
habit.getId(),
)
}
} else {
commandRunner.execute(
CreateRepetitionCommand(
habitList,
habit,
timestamp,
value,
),
null,
)
}
}
interface Screen {
fun showNumberPicker(value: Double,
unit: String,
callback: ListHabitsBehavior.NumberPickerCallback)
fun updateWidgets()
fun refresh()
fun showHistoryEditorDialog(listener: OnToggleCheckmarkListener)
}
}

@ -1,146 +0,0 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.ui.screens.habits.show;
import androidx.annotation.*;
import org.isoron.uhabits.core.commands.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.tasks.*;
import org.isoron.uhabits.core.ui.callbacks.*;
import org.isoron.uhabits.core.utils.*;
import java.io.*;
import java.util.*;
import javax.inject.*;
import static java.lang.Math.*;
public class ShowHabitMenuBehavior
{
private HabitList habitList;
@NonNull
private final Habit habit;
@NonNull
private final TaskRunner taskRunner;
@NonNull
private Screen screen;
@NonNull
private System system;
@NonNull
private CommandRunner commandRunner;
@Inject
public ShowHabitMenuBehavior(@NonNull HabitList habitList,
@NonNull Habit habit,
@NonNull TaskRunner taskRunner,
@NonNull Screen screen,
@NonNull System system,
@NonNull CommandRunner commandRunner)
{
this.habitList = habitList;
this.habit = habit;
this.taskRunner = taskRunner;
this.screen = screen;
this.system = system;
this.commandRunner = commandRunner;
}
public void onEditHabit()
{
screen.showEditHabitScreen(habit);
}
public void onExportCSV()
{
List<Habit> selected = Collections.singletonList(habit);
File outputDir = system.getCSVOutputDir();
taskRunner.execute(
new ExportCSVTask(habitList, selected, outputDir, filename ->
{
if (filename != null) screen.showSendFileScreen(filename);
else screen.showMessage(Message.COULD_NOT_EXPORT);
}));
}
public void onDeleteHabit()
{
List<Habit> selected = Collections.singletonList(habit);
screen.showDeleteConfirmationScreen(() -> {
commandRunner.execute(new DeleteHabitsCommand(habitList, selected),
null);
screen.close();
});
}
public void onRandomize()
{
Random random = new Random();
habit.getRepetitions().removeAll();
double strength = 50;
for (int i = 0; i < 365 * 5; i++)
{
if (i % 7 == 0) strength = max(0, min(100, strength + 10 * random.nextGaussian()));
if (random.nextInt(100) > strength) continue;
int value = Checkmark.YES_MANUAL;
if (habit.isNumerical())
value = (int) (1000 + 250 * random.nextGaussian() * strength / 100) * 1000;
habit.getRepetitions().setValue(DateUtils.getToday().minus(i), value);
}
habit.invalidateNewerThan(Timestamp.ZERO);
}
public enum Message
{
COULD_NOT_EXPORT, HABIT_DELETED
}
public interface Screen
{
void showEditHabitScreen(@NonNull Habit habit);
void showMessage(Message m);
void showSendFileScreen(String filename);
void showDeleteConfirmationScreen(
@NonNull OnConfirmedCallback callback);
void close();
}
public interface System
{
File getCSVOutputDir();
}
}

@ -0,0 +1,91 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.ui.screens.habits.show
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.tasks.*
import org.isoron.uhabits.core.ui.callbacks.*
import org.isoron.uhabits.core.utils.*
import java.io.*
import java.util.*
class ShowHabitMenuBehavior(
private val commandRunner: CommandRunner,
private val habit: Habit,
private val habitList: HabitList,
private val screen: Screen,
private val system: System,
private val taskRunner: TaskRunner,
) {
fun onEditHabit() {
screen.showEditHabitScreen(habit)
}
fun onExportCSV() {
val outputDir = system.getCSVOutputDir()
taskRunner.execute(ExportCSVTask(habitList, listOf(habit), outputDir) { filename: String? ->
if (filename != null) {
screen.showSendFileScreen(filename)
} else {
screen.showMessage(Message.COULD_NOT_EXPORT)
}
})
}
fun onDeleteHabit() {
screen.showDeleteConfirmationScreen {
commandRunner.execute(DeleteHabitsCommand(habitList, listOf(habit)), null)
screen.close()
}
}
fun onRandomize() {
val random = Random()
habit.repetitions.removeAll()
var strength = 50.0
for (i in 0 until 365 * 5) {
if (i % 7 == 0) strength = Math.max(0.0, Math.min(100.0, strength + 10 * random.nextGaussian()))
if (random.nextInt(100) > strength) continue
var value = Checkmark.YES_MANUAL
if (habit.isNumerical) value = (1000 + 250 * random.nextGaussian() * strength / 100).toInt() * 1000
habit.repetitions.setValue(DateUtils.getToday().minus(i), value)
}
habit.invalidateNewerThan(Timestamp.ZERO)
screen.refresh()
}
enum class Message {
COULD_NOT_EXPORT
}
interface Screen {
fun showEditHabitScreen(habit: Habit)
fun showMessage(m: Message?)
fun showSendFileScreen(filename: String)
fun showDeleteConfirmationScreen(
callback: OnConfirmedCallback)
fun close()
fun refresh()
}
interface System {
fun getCSVOutputDir(): File
}
}

@ -49,8 +49,7 @@ public class ShowHabitMenuBehaviorTest extends BaseUnitTest
screen = mock(ShowHabitMenuBehavior.Screen.class);
habit = fixtures.createShortHabit();
menu = new ShowHabitMenuBehavior(habitList, habit, taskRunner, screen,
system, commandRunner);
menu = new ShowHabitMenuBehavior(commandRunner, habit, habitList, screen, system, taskRunner);
}
@Test

Loading…
Cancel
Save