mirror of https://github.com/tasks/tasks
Convert TaskEditFragment to Kotlin
parent
9d91346ddf
commit
29de2d0d53
@ -1,465 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2012 Todoroo Inc
|
|
||||||
*
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.todoroo.astrid.activity;
|
|
||||||
|
|
||||||
import static com.google.common.base.Predicates.not;
|
|
||||||
import static com.google.common.collect.Iterables.filter;
|
|
||||||
import static com.todoroo.andlib.utility.AndroidUtilities.assertNotMainThread;
|
|
||||||
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastQ;
|
|
||||||
import static com.todoroo.andlib.utility.DateUtilities.now;
|
|
||||||
import static org.tasks.Strings.isNullOrEmpty;
|
|
||||||
import static org.tasks.date.DateTimeUtils.newDateTime;
|
|
||||||
import static org.tasks.files.FileHelper.copyToUri;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.drawable.ColorDrawable;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.format.DateUtils;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
|
||||||
import com.google.android.material.appbar.AppBarLayout;
|
|
||||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities;
|
|
||||||
import com.todoroo.astrid.api.Filter;
|
|
||||||
import com.todoroo.astrid.dao.TaskDaoBlocking;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import com.todoroo.astrid.notes.CommentsController;
|
|
||||||
import com.todoroo.astrid.repeats.RepeatControlSet;
|
|
||||||
import com.todoroo.astrid.service.TaskDeleter;
|
|
||||||
import com.todoroo.astrid.timers.TimerPlugin;
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
|
||||||
import io.reactivex.Completable;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.analytics.Firebase;
|
|
||||||
import org.tasks.data.UserActivity;
|
|
||||||
import org.tasks.data.UserActivityDaoBlocking;
|
|
||||||
import org.tasks.databinding.FragmentTaskEditBinding;
|
|
||||||
import org.tasks.dialogs.DialogBuilder;
|
|
||||||
import org.tasks.dialogs.Linkify;
|
|
||||||
import org.tasks.fragments.TaskEditControlSetFragmentManager;
|
|
||||||
import org.tasks.notifications.NotificationManager;
|
|
||||||
import org.tasks.preferences.Preferences;
|
|
||||||
import org.tasks.themes.ThemeColor;
|
|
||||||
import org.tasks.ui.SubtaskControlSet;
|
|
||||||
import org.tasks.ui.TaskEditControlFragment;
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
public final class TaskEditFragment extends Fragment
|
|
||||||
implements Toolbar.OnMenuItemClickListener {
|
|
||||||
|
|
||||||
static final String TAG_TASKEDIT_FRAGMENT = "taskedit_fragment";
|
|
||||||
private static final String EXTRA_TASK = "extra_task";
|
|
||||||
private static final String EXTRA_THEME = "extra_theme";
|
|
||||||
private static final String EXTRA_COMPLETED = "extra_completed";
|
|
||||||
|
|
||||||
@Inject TaskDaoBlocking taskDao;
|
|
||||||
@Inject UserActivityDaoBlocking userActivityDao;
|
|
||||||
@Inject TaskDeleter taskDeleter;
|
|
||||||
@Inject NotificationManager notificationManager;
|
|
||||||
@Inject DialogBuilder dialogBuilder;
|
|
||||||
@Inject Activity context;
|
|
||||||
@Inject TaskEditControlSetFragmentManager taskEditControlSetFragmentManager;
|
|
||||||
@Inject CommentsController commentsController;
|
|
||||||
@Inject Preferences preferences;
|
|
||||||
@Inject Firebase firebase;
|
|
||||||
@Inject TimerPlugin timerPlugin;
|
|
||||||
@Inject Linkify linkify;
|
|
||||||
|
|
||||||
Task model = null;
|
|
||||||
private TaskEditFragmentCallbackHandler callback;
|
|
||||||
private boolean showKeyboard;
|
|
||||||
private FragmentTaskEditBinding binding;
|
|
||||||
private boolean completed;
|
|
||||||
|
|
||||||
static TaskEditFragment newTaskEditFragment(Task task, ThemeColor themeColor) {
|
|
||||||
TaskEditFragment taskEditFragment = new TaskEditFragment();
|
|
||||||
Bundle arguments = new Bundle();
|
|
||||||
arguments.putParcelable(EXTRA_TASK, task);
|
|
||||||
arguments.putParcelable(EXTRA_THEME, themeColor);
|
|
||||||
taskEditFragment.setArguments(arguments);
|
|
||||||
return taskEditFragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity) {
|
|
||||||
super.onAttach(activity);
|
|
||||||
|
|
||||||
callback = (TaskEditFragmentCallbackHandler) activity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
outState.putBoolean(EXTRA_COMPLETED, completed);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
binding = FragmentTaskEditBinding.inflate(inflater);
|
|
||||||
View view = binding.getRoot();
|
|
||||||
|
|
||||||
Bundle arguments = getArguments();
|
|
||||||
model = arguments.getParcelable(EXTRA_TASK);
|
|
||||||
ThemeColor themeColor = arguments.getParcelable(EXTRA_THEME);
|
|
||||||
|
|
||||||
Toolbar toolbar = binding.toolbar;
|
|
||||||
toolbar.setNavigationIcon(context.getDrawable(R.drawable.ic_outline_save_24px));
|
|
||||||
toolbar.setNavigationOnClickListener(v -> save());
|
|
||||||
|
|
||||||
boolean backButtonSavesTask = preferences.backButtonSavesTask();
|
|
||||||
toolbar.inflateMenu(R.menu.menu_task_edit_fragment);
|
|
||||||
Menu menu = toolbar.getMenu();
|
|
||||||
MenuItem delete = menu.findItem(R.id.menu_delete);
|
|
||||||
delete.setVisible(!model.isNew());
|
|
||||||
delete.setShowAsAction(
|
|
||||||
backButtonSavesTask ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
|
||||||
MenuItem discard = menu.findItem(R.id.menu_discard);
|
|
||||||
discard.setVisible(backButtonSavesTask);
|
|
||||||
discard.setShowAsAction(
|
|
||||||
model.isNew() ? MenuItem.SHOW_AS_ACTION_IF_ROOM : MenuItem.SHOW_AS_ACTION_NEVER);
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
showKeyboard = model.isNew() && isNullOrEmpty(model.getTitle());
|
|
||||||
completed = model.isCompleted();
|
|
||||||
} else {
|
|
||||||
completed = savedInstanceState.getBoolean(EXTRA_COMPLETED);
|
|
||||||
}
|
|
||||||
|
|
||||||
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) binding.appbarlayout.getLayoutParams();
|
|
||||||
params.setBehavior(new AppBarLayout.Behavior());
|
|
||||||
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
|
|
||||||
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
|
|
||||||
@Override
|
|
||||||
public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
toolbar.setOnMenuItemClickListener(this);
|
|
||||||
themeColor.apply(binding.collapsingtoolbarlayout, toolbar);
|
|
||||||
EditText title = binding.title;
|
|
||||||
title.setText(model.getTitle());
|
|
||||||
title.setHorizontallyScrolling(false);
|
|
||||||
title.setTextColor(themeColor.getColorOnPrimary());
|
|
||||||
title.setHintTextColor(themeColor.getHintOnPrimary());
|
|
||||||
title.setMaxLines(5);
|
|
||||||
if (model.isNew() || preferences.getBoolean(R.string.p_hide_check_button, false)) {
|
|
||||||
binding.fab.setVisibility(View.INVISIBLE);
|
|
||||||
} else if (completed) {
|
|
||||||
title.setPaintFlags(title.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
|
|
||||||
binding.fab.setImageResource(R.drawable.ic_outline_check_box_outline_blank_24px);
|
|
||||||
}
|
|
||||||
binding.fab.setOnClickListener(v -> {
|
|
||||||
if (completed) {
|
|
||||||
completed = false;
|
|
||||||
title.setPaintFlags(title.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
|
|
||||||
binding.fab.setImageResource(R.drawable.ic_outline_check_box_24px);
|
|
||||||
} else {
|
|
||||||
completed = true;
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (atLeastQ()) {
|
|
||||||
title.setVerticalScrollbarThumbDrawable(new ColorDrawable(themeColor.getHintOnPrimary()));
|
|
||||||
}
|
|
||||||
binding.appbarlayout.addOnOffsetChangedListener(
|
|
||||||
(appBarLayout, verticalOffset) -> {
|
|
||||||
if (verticalOffset == 0) {
|
|
||||||
title.setVisibility(View.VISIBLE);
|
|
||||||
binding.collapsingtoolbarlayout.setTitleEnabled(false);
|
|
||||||
} else if (Math.abs(verticalOffset) < appBarLayout.getTotalScrollRange()) {
|
|
||||||
title.setVisibility(View.INVISIBLE);
|
|
||||||
binding.collapsingtoolbarlayout.setTitle(title.getText());
|
|
||||||
binding.collapsingtoolbarlayout.setTitleEnabled(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!model.isNew()) {
|
|
||||||
notificationManager.cancel(model.getId());
|
|
||||||
|
|
||||||
if (preferences.getBoolean(R.string.p_linkify_task_edit, false)) {
|
|
||||||
linkify.linkify(title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
commentsController.initialize(model, binding.comments);
|
|
||||||
commentsController.reloadView();
|
|
||||||
|
|
||||||
FragmentManager fragmentManager = getChildFragmentManager();
|
|
||||||
List<TaskEditControlFragment> taskEditControlFragments =
|
|
||||||
taskEditControlSetFragmentManager.getOrCreateFragments(this, model);
|
|
||||||
int visibleSize = taskEditControlSetFragmentManager.getVisibleSize();
|
|
||||||
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
|
||||||
for (int i = 0; i < taskEditControlFragments.size(); i++) {
|
|
||||||
TaskEditControlFragment taskEditControlFragment = taskEditControlFragments.get(i);
|
|
||||||
String tag = getString(taskEditControlFragment.controlId());
|
|
||||||
fragmentTransaction.replace(
|
|
||||||
TaskEditControlSetFragmentManager.TASK_EDIT_CONTROL_FRAGMENT_ROWS[i],
|
|
||||||
taskEditControlFragment,
|
|
||||||
tag);
|
|
||||||
if (i >= visibleSize) {
|
|
||||||
fragmentTransaction.hide(taskEditControlFragment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragmentTransaction.commit();
|
|
||||||
|
|
||||||
for (int i = visibleSize - 1; i > 0; i--) {
|
|
||||||
binding.controlSets.addView(inflater.inflate(R.layout.task_edit_row_divider, binding.controlSets, false), i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
|
|
||||||
if (showKeyboard) {
|
|
||||||
binding.title.requestFocus();
|
|
||||||
InputMethodManager imm =
|
|
||||||
(InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
||||||
imm.showSoftInput(binding.title, InputMethodManager.SHOW_IMPLICIT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onMenuItemClick(MenuItem item) {
|
|
||||||
AndroidUtilities.hideKeyboard(getActivity());
|
|
||||||
|
|
||||||
if (item.getItemId() == R.id.menu_delete) {
|
|
||||||
deleteButtonClick();
|
|
||||||
return true;
|
|
||||||
} else if (item.getItemId() == R.id.menu_discard) {
|
|
||||||
discardButtonClick();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task stopTimer() {
|
|
||||||
timerPlugin.stopTimer(model);
|
|
||||||
String elapsedTime = DateUtils.formatElapsedTime(model.getElapsedSeconds());
|
|
||||||
addComment(
|
|
||||||
String.format(
|
|
||||||
"%s %s\n%s %s", // $NON-NLS-1$
|
|
||||||
getString(R.string.TEA_timer_comment_stopped),
|
|
||||||
DateUtilities.getTimeString(context, newDateTime()),
|
|
||||||
getString(R.string.TEA_timer_comment_spent),
|
|
||||||
elapsedTime),
|
|
||||||
null);
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task startTimer() {
|
|
||||||
timerPlugin.startTimer(model);
|
|
||||||
addComment(
|
|
||||||
String.format(
|
|
||||||
"%s %s",
|
|
||||||
getString(R.string.TEA_timer_comment_started),
|
|
||||||
DateUtilities.getTimeString(context, newDateTime())),
|
|
||||||
null);
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Save task model from values in UI components */
|
|
||||||
public void save() {
|
|
||||||
List<TaskEditControlFragment> fragments =
|
|
||||||
taskEditControlSetFragmentManager.getFragmentsInPersistOrder(getChildFragmentManager());
|
|
||||||
if (hasChanges(fragments)) {
|
|
||||||
boolean isNewTask = model.isNew();
|
|
||||||
TaskListFragment taskListFragment = ((MainActivity) getActivity()).getTaskListFragment();
|
|
||||||
String title = getTitle();
|
|
||||||
model.setTitle(isNullOrEmpty(title) ? getString(R.string.no_title) : title);
|
|
||||||
if (completed != model.isCompleted()) {
|
|
||||||
model.setCompletionDate(completed ? now() : 0);
|
|
||||||
}
|
|
||||||
for (TaskEditControlFragment fragment :
|
|
||||||
filter(fragments, not(TaskEditControlFragment::requiresId))) {
|
|
||||||
fragment.applyBlocking(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
Completable.fromAction(
|
|
||||||
() -> {
|
|
||||||
assertNotMainThread();
|
|
||||||
|
|
||||||
if (isNewTask) {
|
|
||||||
taskDao.createNew(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TaskEditControlFragment fragment :
|
|
||||||
filter(fragments, TaskEditControlFragment::requiresId)) {
|
|
||||||
fragment.applyBlocking(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
taskDao.save(model, null);
|
|
||||||
|
|
||||||
if (isNewTask) {
|
|
||||||
taskListFragment.onTaskCreated(model.getUuid());
|
|
||||||
if (!isNullOrEmpty(model.getCalendarURI())) {
|
|
||||||
taskListFragment.makeSnackbar(R.string.calendar_event_created, model.getTitle())
|
|
||||||
.setAction(R.string.action_open, v -> {
|
|
||||||
String uri = model.getCalendarURI();
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
|
|
||||||
taskListFragment.startActivity(intent);
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe();
|
|
||||||
callback.removeTaskEditFragment();
|
|
||||||
} else {
|
|
||||||
discard();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ======================================================================
|
|
||||||
* =============================================== model reading / saving
|
|
||||||
* ======================================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
private RepeatControlSet getRepeatControlSet() {
|
|
||||||
return getFragment(RepeatControlSet.TAG);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SubtaskControlSet getSubtaskControlSet() {
|
|
||||||
return getFragment(SubtaskControlSet.TAG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <T extends TaskEditControlFragment> T getFragment(int tag) {
|
|
||||||
return (T) getChildFragmentManager().findFragmentByTag(getString(tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getTitle() {
|
|
||||||
return binding.title.getText().toString().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasChanges(List<TaskEditControlFragment> fragments) {
|
|
||||||
String newTitle = getTitle();
|
|
||||||
if (!newTitle.equals(model.getTitle())
|
|
||||||
|| (!model.isNew() && completed != model.isCompleted())
|
|
||||||
|| (model.isNew() && !isNullOrEmpty(newTitle))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (TaskEditControlFragment fragment : fragments) {
|
|
||||||
if (fragment.hasChangesBlocking(model)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
firebase.reportException(e);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ======================================================================
|
|
||||||
* ======================================================= event handlers
|
|
||||||
* ======================================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
void discardButtonClick() {
|
|
||||||
if (hasChanges(
|
|
||||||
taskEditControlSetFragmentManager.getFragmentsInPersistOrder(getChildFragmentManager()))) {
|
|
||||||
dialogBuilder
|
|
||||||
.newDialog(R.string.discard_confirmation)
|
|
||||||
.setPositiveButton(R.string.keep_editing, null)
|
|
||||||
.setNegativeButton(R.string.discard, (dialog, which) -> discard())
|
|
||||||
.show();
|
|
||||||
} else {
|
|
||||||
discard();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void discard() {
|
|
||||||
if (model != null && model.isNew()) {
|
|
||||||
timerPlugin.stopTimer(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback.removeTaskEditFragment();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteButtonClick() {
|
|
||||||
dialogBuilder
|
|
||||||
.newDialog(R.string.DLG_delete_this_task_question)
|
|
||||||
.setPositiveButton(
|
|
||||||
android.R.string.ok,
|
|
||||||
(dialog, which) -> {
|
|
||||||
taskDeleter.markDeleted(model);
|
|
||||||
callback.removeTaskEditFragment();
|
|
||||||
})
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ======================================================================
|
|
||||||
* ========================================== UI component helper classes
|
|
||||||
* ======================================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
void onDueDateChanged(long dueDate) {
|
|
||||||
RepeatControlSet repeatControlSet = getRepeatControlSet();
|
|
||||||
if (repeatControlSet != null) {
|
|
||||||
repeatControlSet.onDueDateChanged(dueDate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onRemoteListChanged(@Nullable Filter filter) {
|
|
||||||
SubtaskControlSet subtaskControlSet = getSubtaskControlSet();
|
|
||||||
if (subtaskControlSet != null) {
|
|
||||||
subtaskControlSet.onRemoteListChanged(filter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void addComment(String message, Uri picture) {
|
|
||||||
UserActivity userActivity = new UserActivity();
|
|
||||||
if (picture != null) {
|
|
||||||
Uri output = copyToUri(context, preferences.getAttachmentsDirectory(), picture);
|
|
||||||
userActivity.setPicture(output);
|
|
||||||
}
|
|
||||||
userActivity.setMessage(message);
|
|
||||||
userActivity.setTargetId(model.getUuid());
|
|
||||||
userActivity.setCreated(now());
|
|
||||||
userActivityDao.createNew(userActivity);
|
|
||||||
commentsController.reloadView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface TaskEditFragmentCallbackHandler {
|
|
||||||
|
|
||||||
void removeTaskEditFragment();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,403 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Todoroo Inc
|
||||||
|
*
|
||||||
|
* See the file "LICENSE" for the full license governing this code.
|
||||||
|
*/
|
||||||
|
package com.todoroo.astrid.activity
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import com.google.android.material.appbar.AppBarLayout.Behavior.DragCallback
|
||||||
|
import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener
|
||||||
|
import com.google.common.base.Predicates
|
||||||
|
import com.google.common.collect.Iterables
|
||||||
|
import com.todoroo.andlib.utility.AndroidUtilities
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities
|
||||||
|
import com.todoroo.astrid.api.Filter
|
||||||
|
import com.todoroo.astrid.dao.TaskDaoBlocking
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import com.todoroo.astrid.notes.CommentsController
|
||||||
|
import com.todoroo.astrid.repeats.RepeatControlSet
|
||||||
|
import com.todoroo.astrid.service.TaskDeleter
|
||||||
|
import com.todoroo.astrid.timers.TimerPlugin
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import io.reactivex.Completable
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.Strings.isNullOrEmpty
|
||||||
|
import org.tasks.analytics.Firebase
|
||||||
|
import org.tasks.data.UserActivity
|
||||||
|
import org.tasks.data.UserActivityDaoBlocking
|
||||||
|
import org.tasks.databinding.FragmentTaskEditBinding
|
||||||
|
import org.tasks.date.DateTimeUtils.newDateTime
|
||||||
|
import org.tasks.dialogs.DialogBuilder
|
||||||
|
import org.tasks.dialogs.Linkify
|
||||||
|
import org.tasks.files.FileHelper
|
||||||
|
import org.tasks.fragments.TaskEditControlSetFragmentManager
|
||||||
|
import org.tasks.notifications.NotificationManager
|
||||||
|
import org.tasks.preferences.Preferences
|
||||||
|
import org.tasks.themes.ThemeColor
|
||||||
|
import org.tasks.ui.SubtaskControlSet
|
||||||
|
import org.tasks.ui.TaskEditControlFragment
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
|
@Inject lateinit var taskDao: TaskDaoBlocking
|
||||||
|
@Inject lateinit var userActivityDao: UserActivityDaoBlocking
|
||||||
|
@Inject lateinit var taskDeleter: TaskDeleter
|
||||||
|
@Inject lateinit var notificationManager: NotificationManager
|
||||||
|
@Inject lateinit var dialogBuilder: DialogBuilder
|
||||||
|
@Inject lateinit var context: Activity
|
||||||
|
@Inject lateinit var taskEditControlSetFragmentManager: TaskEditControlSetFragmentManager
|
||||||
|
@Inject lateinit var commentsController: CommentsController
|
||||||
|
@Inject lateinit var preferences: Preferences
|
||||||
|
@Inject lateinit var firebase: Firebase
|
||||||
|
@Inject lateinit var timerPlugin: TimerPlugin
|
||||||
|
@Inject lateinit var linkify: Linkify
|
||||||
|
|
||||||
|
lateinit var model: Task
|
||||||
|
lateinit var binding: FragmentTaskEditBinding
|
||||||
|
private var callback: TaskEditFragmentCallbackHandler? = null
|
||||||
|
private var showKeyboard = false
|
||||||
|
private var completed = false
|
||||||
|
|
||||||
|
override fun onAttach(activity: Activity) {
|
||||||
|
super.onAttach(activity)
|
||||||
|
callback = activity as TaskEditFragmentCallbackHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putBoolean(EXTRA_COMPLETED, completed)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
binding = FragmentTaskEditBinding.inflate(inflater)
|
||||||
|
val view: View = binding.root
|
||||||
|
val arguments = requireArguments()
|
||||||
|
model = arguments.getParcelable(EXTRA_TASK)!!
|
||||||
|
val themeColor: ThemeColor = arguments.getParcelable(EXTRA_THEME)!!
|
||||||
|
val toolbar = binding.toolbar
|
||||||
|
toolbar.navigationIcon = context.getDrawable(R.drawable.ic_outline_save_24px)
|
||||||
|
toolbar.setNavigationOnClickListener { save() }
|
||||||
|
val backButtonSavesTask = preferences.backButtonSavesTask()
|
||||||
|
toolbar.inflateMenu(R.menu.menu_task_edit_fragment)
|
||||||
|
val menu = toolbar.menu
|
||||||
|
val delete = menu.findItem(R.id.menu_delete)
|
||||||
|
delete.isVisible = !model.isNew
|
||||||
|
delete.setShowAsAction(
|
||||||
|
if (backButtonSavesTask) MenuItem.SHOW_AS_ACTION_NEVER else MenuItem.SHOW_AS_ACTION_IF_ROOM)
|
||||||
|
val discard = menu.findItem(R.id.menu_discard)
|
||||||
|
discard.isVisible = backButtonSavesTask
|
||||||
|
discard.setShowAsAction(
|
||||||
|
if (model.isNew) MenuItem.SHOW_AS_ACTION_IF_ROOM else MenuItem.SHOW_AS_ACTION_NEVER)
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
showKeyboard = model.isNew && isNullOrEmpty(model.title)
|
||||||
|
completed = model.isCompleted
|
||||||
|
} else {
|
||||||
|
completed = savedInstanceState.getBoolean(EXTRA_COMPLETED)
|
||||||
|
}
|
||||||
|
val params = binding.appbarlayout.layoutParams as CoordinatorLayout.LayoutParams
|
||||||
|
params.behavior = AppBarLayout.Behavior()
|
||||||
|
val behavior = params.behavior as AppBarLayout.Behavior?
|
||||||
|
behavior!!.setDragCallback(object : DragCallback() {
|
||||||
|
override fun canDrag(appBarLayout: AppBarLayout): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
toolbar.setOnMenuItemClickListener(this)
|
||||||
|
themeColor.apply(binding.collapsingtoolbarlayout, toolbar)
|
||||||
|
val title = binding.title
|
||||||
|
title.setText(model.title)
|
||||||
|
title.setHorizontallyScrolling(false)
|
||||||
|
title.setTextColor(themeColor.colorOnPrimary)
|
||||||
|
title.setHintTextColor(themeColor.hintOnPrimary)
|
||||||
|
title.maxLines = 5
|
||||||
|
if (model.isNew || preferences.getBoolean(R.string.p_hide_check_button, false)) {
|
||||||
|
binding.fab.visibility = View.INVISIBLE
|
||||||
|
} else if (completed) {
|
||||||
|
title.paintFlags = title.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
|
||||||
|
binding.fab.setImageResource(R.drawable.ic_outline_check_box_outline_blank_24px)
|
||||||
|
}
|
||||||
|
binding.fab.setOnClickListener {
|
||||||
|
if (completed) {
|
||||||
|
completed = false
|
||||||
|
title.paintFlags = title.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
|
||||||
|
binding.fab.setImageResource(R.drawable.ic_outline_check_box_24px)
|
||||||
|
} else {
|
||||||
|
completed = true
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (AndroidUtilities.atLeastQ()) {
|
||||||
|
title.verticalScrollbarThumbDrawable = ColorDrawable(themeColor.hintOnPrimary)
|
||||||
|
}
|
||||||
|
binding.appbarlayout.addOnOffsetChangedListener(
|
||||||
|
OnOffsetChangedListener { appBarLayout: AppBarLayout, verticalOffset: Int ->
|
||||||
|
if (verticalOffset == 0) {
|
||||||
|
title.visibility = View.VISIBLE
|
||||||
|
binding.collapsingtoolbarlayout.isTitleEnabled = false
|
||||||
|
} else if (abs(verticalOffset) < appBarLayout.totalScrollRange) {
|
||||||
|
title.visibility = View.INVISIBLE
|
||||||
|
binding.collapsingtoolbarlayout.title = title.text
|
||||||
|
binding.collapsingtoolbarlayout.isTitleEnabled = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!model.isNew) {
|
||||||
|
notificationManager.cancel(model.id)
|
||||||
|
if (preferences.getBoolean(R.string.p_linkify_task_edit, false)) {
|
||||||
|
linkify.linkify(title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commentsController.initialize(model, binding.comments)
|
||||||
|
commentsController.reloadView()
|
||||||
|
val fragmentManager = childFragmentManager
|
||||||
|
val taskEditControlFragments = taskEditControlSetFragmentManager.getOrCreateFragments(this, model)
|
||||||
|
val visibleSize = taskEditControlSetFragmentManager.visibleSize
|
||||||
|
val fragmentTransaction = fragmentManager.beginTransaction()
|
||||||
|
for (i in taskEditControlFragments.indices) {
|
||||||
|
val taskEditControlFragment = taskEditControlFragments[i]
|
||||||
|
val tag = getString(taskEditControlFragment.controlId())
|
||||||
|
fragmentTransaction.replace(
|
||||||
|
TaskEditControlSetFragmentManager.TASK_EDIT_CONTROL_FRAGMENT_ROWS[i],
|
||||||
|
taskEditControlFragment,
|
||||||
|
tag)
|
||||||
|
if (i >= visibleSize) {
|
||||||
|
fragmentTransaction.hide(taskEditControlFragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragmentTransaction.commit()
|
||||||
|
for (i in visibleSize - 1 downTo 1) {
|
||||||
|
binding.controlSets.addView(inflater.inflate(R.layout.task_edit_row_divider, binding.controlSets, false), i)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
if (showKeyboard) {
|
||||||
|
binding.title.requestFocus()
|
||||||
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.showSoftInput(binding.title, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||||
|
AndroidUtilities.hideKeyboard(activity)
|
||||||
|
if (item.itemId == R.id.menu_delete) {
|
||||||
|
deleteButtonClick()
|
||||||
|
return true
|
||||||
|
} else if (item.itemId == R.id.menu_discard) {
|
||||||
|
discardButtonClick()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopTimer(): Task {
|
||||||
|
timerPlugin.stopTimer(model)
|
||||||
|
val elapsedTime = DateUtils.formatElapsedTime(model.elapsedSeconds.toLong())
|
||||||
|
addComment(String.format(
|
||||||
|
"%s %s\n%s %s", // $NON-NLS-1$
|
||||||
|
getString(R.string.TEA_timer_comment_stopped),
|
||||||
|
DateUtilities.getTimeString(context, newDateTime()),
|
||||||
|
getString(R.string.TEA_timer_comment_spent),
|
||||||
|
elapsedTime),
|
||||||
|
null)
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startTimer(): Task {
|
||||||
|
timerPlugin.startTimer(model)
|
||||||
|
addComment(String.format(
|
||||||
|
"%s %s",
|
||||||
|
getString(R.string.TEA_timer_comment_started),
|
||||||
|
DateUtilities.getTimeString(context, newDateTime())),
|
||||||
|
null)
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Save task model from values in UI components */
|
||||||
|
fun save() {
|
||||||
|
val fragments = taskEditControlSetFragmentManager.getFragmentsInPersistOrder(childFragmentManager)
|
||||||
|
if (hasChanges(fragments)) {
|
||||||
|
val isNewTask = model.isNew
|
||||||
|
val taskListFragment = (activity as MainActivity?)!!.taskListFragment
|
||||||
|
val title = title
|
||||||
|
model.title = if (isNullOrEmpty(title)) getString(R.string.no_title) else title
|
||||||
|
if (completed != model.isCompleted) {
|
||||||
|
model.completionDate = if (completed) DateUtilities.now() else 0
|
||||||
|
}
|
||||||
|
for (fragment in Iterables.filter(fragments, Predicates.not { obj: TaskEditControlFragment? -> obj!!.requiresId() })) {
|
||||||
|
fragment.applyBlocking(model)
|
||||||
|
}
|
||||||
|
Completable.fromAction {
|
||||||
|
AndroidUtilities.assertNotMainThread()
|
||||||
|
if (isNewTask) {
|
||||||
|
taskDao.createNew(model)
|
||||||
|
}
|
||||||
|
for (fragment in Iterables.filter(fragments) { obj: TaskEditControlFragment? -> obj!!.requiresId() }) {
|
||||||
|
fragment.applyBlocking(model)
|
||||||
|
}
|
||||||
|
taskDao.save(model, null)
|
||||||
|
if (isNewTask) {
|
||||||
|
taskListFragment!!.onTaskCreated(model.uuid)
|
||||||
|
if (!isNullOrEmpty(model.calendarURI)) {
|
||||||
|
taskListFragment.makeSnackbar(R.string.calendar_event_created, model.title)
|
||||||
|
.setAction(R.string.action_open) { v: View? ->
|
||||||
|
val uri = model.calendarURI
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri))
|
||||||
|
taskListFragment.startActivity(intent)
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe()
|
||||||
|
callback!!.removeTaskEditFragment()
|
||||||
|
} else {
|
||||||
|
discard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ======================================================================
|
||||||
|
* =============================================== model reading / saving
|
||||||
|
* ======================================================================
|
||||||
|
*/
|
||||||
|
private val repeatControlSet: RepeatControlSet
|
||||||
|
get() = getFragment<RepeatControlSet>(RepeatControlSet.TAG)!!
|
||||||
|
|
||||||
|
private val subtaskControlSet: SubtaskControlSet
|
||||||
|
get() = getFragment<SubtaskControlSet>(SubtaskControlSet.TAG)!!
|
||||||
|
|
||||||
|
private fun <T : TaskEditControlFragment?> getFragment(tag: Int): T? {
|
||||||
|
return childFragmentManager.findFragmentByTag(getString(tag)) as T?
|
||||||
|
}
|
||||||
|
|
||||||
|
private val title: String
|
||||||
|
get() = binding.title.text.toString().trim { it <= ' ' }
|
||||||
|
|
||||||
|
private fun hasChanges(fragments: List<TaskEditControlFragment>): Boolean {
|
||||||
|
val newTitle = title
|
||||||
|
if (newTitle != model.title
|
||||||
|
|| !model.isNew && completed != model.isCompleted
|
||||||
|
|| model.isNew && !isNullOrEmpty(newTitle)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
for (fragment in fragments) {
|
||||||
|
if (fragment.hasChangesBlocking(model)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
firebase.reportException(e)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ======================================================================
|
||||||
|
* ======================================================= event handlers
|
||||||
|
* ======================================================================
|
||||||
|
*/
|
||||||
|
fun discardButtonClick() {
|
||||||
|
if (hasChanges(
|
||||||
|
taskEditControlSetFragmentManager.getFragmentsInPersistOrder(childFragmentManager))) {
|
||||||
|
dialogBuilder
|
||||||
|
.newDialog(R.string.discard_confirmation)
|
||||||
|
.setPositiveButton(R.string.keep_editing, null)
|
||||||
|
.setNegativeButton(R.string.discard) { _, _ -> discard() }
|
||||||
|
.show()
|
||||||
|
} else {
|
||||||
|
discard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun discard() {
|
||||||
|
if (model.isNew) {
|
||||||
|
timerPlugin.stopTimer(model)
|
||||||
|
}
|
||||||
|
callback!!.removeTaskEditFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteButtonClick() {
|
||||||
|
dialogBuilder
|
||||||
|
.newDialog(R.string.DLG_delete_this_task_question)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
taskDeleter.markDeleted(model)
|
||||||
|
callback!!.removeTaskEditFragment()
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ======================================================================
|
||||||
|
* ========================================== UI component helper classes
|
||||||
|
* ======================================================================
|
||||||
|
*/
|
||||||
|
fun onDueDateChanged(dueDate: Long) {
|
||||||
|
val repeatControlSet: RepeatControlSet? = repeatControlSet
|
||||||
|
repeatControlSet?.onDueDateChanged(dueDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onRemoteListChanged(filter: Filter?) {
|
||||||
|
val subtaskControlSet: SubtaskControlSet? = subtaskControlSet
|
||||||
|
subtaskControlSet?.onRemoteListChanged(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addComment(message: String?, picture: Uri?) {
|
||||||
|
val userActivity = UserActivity()
|
||||||
|
if (picture != null) {
|
||||||
|
val output = FileHelper.copyToUri(context, preferences.attachmentsDirectory, picture)
|
||||||
|
userActivity.setPicture(output)
|
||||||
|
}
|
||||||
|
userActivity.message = message
|
||||||
|
userActivity.targetId = model.uuid
|
||||||
|
userActivity.created = DateUtilities.now()
|
||||||
|
userActivityDao.createNew(userActivity)
|
||||||
|
commentsController.reloadView()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TaskEditFragmentCallbackHandler {
|
||||||
|
fun removeTaskEditFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG_TASKEDIT_FRAGMENT = "taskedit_fragment"
|
||||||
|
private const val EXTRA_TASK = "extra_task"
|
||||||
|
private const val EXTRA_THEME = "extra_theme"
|
||||||
|
private const val EXTRA_COMPLETED = "extra_completed"
|
||||||
|
fun newTaskEditFragment(task: Task?, themeColor: ThemeColor?): TaskEditFragment {
|
||||||
|
val taskEditFragment = TaskEditFragment()
|
||||||
|
val arguments = Bundle()
|
||||||
|
arguments.putParcelable(EXTRA_TASK, task)
|
||||||
|
arguments.putParcelable(EXTRA_THEME, themeColor)
|
||||||
|
taskEditFragment.arguments = arguments
|
||||||
|
return taskEditFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue