From 3801c46f3155e97283fe2fdb7fda235f04193b73 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Mon, 11 May 2020 16:30:37 -0500 Subject: [PATCH] Convert TaskListFragment to Kotlin --- .../todoroo/astrid/activity/MainActivity.kt | 4 +- .../astrid/activity/TaskListFragment.java | 883 ------------------ .../astrid/activity/TaskListFragment.kt | 771 +++++++++++++++ 3 files changed, 773 insertions(+), 885 deletions(-) delete mode 100644 app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.java create mode 100644 app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt diff --git a/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt b/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt index edff9cdf6..030b8aa8a 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt @@ -200,7 +200,7 @@ class MainActivity : InjectingAppCompatActivity(), TaskListFragmentCallbackHandl } else { val existing = taskListFragment openTaskListFragment( - if (existing == null || existing.filter !== filter) TaskListFragment.newTaskListFragment(applicationContext, filter) else existing, + if (existing == null || existing.getFilter() !== filter) TaskListFragment.newTaskListFragment(applicationContext, filter) else existing, false) openTask(filter) } @@ -237,7 +237,7 @@ class MainActivity : InjectingAppCompatActivity(), TaskListFragmentCallbackHandl private fun openTaskListFragment(taskListFragment: TaskListFragment, force: Boolean) { AndroidUtilities.assertMainThread() - val newFilter = taskListFragment.filter + val newFilter = taskListFragment.getFilter() if (filter != null && !force && filter!!.areItemsTheSame(newFilter) && filter!!.areContentsTheSame(newFilter)) { diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.java b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.java deleted file mode 100644 index f49011ea5..000000000 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.java +++ /dev/null @@ -1,883 +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 android.app.Activity.RESULT_OK; -import static com.google.common.base.Strings.padStart; -import static com.google.common.collect.Lists.transform; -import static com.todoroo.andlib.utility.AndroidUtilities.assertMainThread; -import static org.tasks.activities.RemoteListPicker.newRemoteListSupportPicker; -import static org.tasks.caldav.CaldavCalendarSettingsActivity.EXTRA_CALDAV_CALENDAR; -import static org.tasks.db.DbUtils.collect; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.Parcelable; -import android.speech.RecognizerIntent; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.MenuItem.OnActionExpandListener; -import android.view.View; -import android.view.ViewGroup; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.view.ActionMode; -import androidx.appcompat.view.ActionMode.Callback; -import androidx.appcompat.widget.SearchView; -import androidx.appcompat.widget.SearchView.OnQueryTextListener; -import androidx.appcompat.widget.Toolbar; -import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.util.Pair; -import androidx.fragment.app.FragmentManager; -import androidx.lifecycle.ViewModelProvider; -import androidx.paging.PagedList; -import androidx.recyclerview.widget.DefaultItemAnimator; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import com.google.android.material.snackbar.Snackbar; -import com.google.common.base.Joiner; -import com.google.common.primitives.Longs; -import com.todoroo.astrid.adapter.TaskAdapter; -import com.todoroo.astrid.adapter.TaskAdapterProvider; -import com.todoroo.astrid.api.CaldavFilter; -import com.todoroo.astrid.api.Filter; -import com.todoroo.astrid.api.GtasksFilter; -import com.todoroo.astrid.api.IdListFilter; -import com.todoroo.astrid.api.SearchFilter; -import com.todoroo.astrid.api.TagFilter; -import com.todoroo.astrid.core.BuiltInFilterExposer; -import com.todoroo.astrid.dao.TaskDao; -import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.service.TaskCreator; -import com.todoroo.astrid.service.TaskDeleter; -import com.todoroo.astrid.service.TaskDuplicator; -import com.todoroo.astrid.service.TaskMover; -import com.todoroo.astrid.timers.TimerPlugin; -import com.todoroo.astrid.utility.Flags; -import io.reactivex.Single; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import io.reactivex.subjects.PublishSubject; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import javax.inject.Inject; -import org.tasks.LocalBroadcastManager; -import org.tasks.R; -import org.tasks.ShortcutManager; -import org.tasks.activities.FilterSettingsActivity; -import org.tasks.activities.GoogleTaskListSettingsActivity; -import org.tasks.activities.PlaceSettingsActivity; -import org.tasks.activities.RemoteListPicker; -import org.tasks.activities.TagSettingsActivity; -import org.tasks.caldav.CaldavCalendarSettingsActivity; -import org.tasks.data.CaldavAccount; -import org.tasks.data.CaldavCalendar; -import org.tasks.data.CaldavDao; -import org.tasks.data.Place; -import org.tasks.data.TagDataDao; -import org.tasks.data.TaskContainer; -import org.tasks.dialogs.DateTimePicker; -import org.tasks.dialogs.DialogBuilder; -import org.tasks.dialogs.SortDialog; -import org.tasks.etesync.EteSyncCalendarSettingsActivity; -import org.tasks.filters.PlaceFilter; -import org.tasks.injection.ForActivity; -import org.tasks.injection.FragmentComponent; -import org.tasks.injection.InjectingFragment; -import org.tasks.intents.TaskIntents; -import org.tasks.notifications.NotificationManager; -import org.tasks.preferences.Device; -import org.tasks.preferences.Preferences; -import org.tasks.sync.SyncAdapters; -import org.tasks.tags.TagPickerActivity; -import org.tasks.tasklist.DragAndDropRecyclerAdapter; -import org.tasks.tasklist.PagedListRecyclerAdapter; -import org.tasks.tasklist.TaskListRecyclerAdapter; -import org.tasks.tasklist.ViewHolderFactory; -import org.tasks.themes.ColorProvider; -import org.tasks.themes.ThemeColor; -import org.tasks.ui.TaskListViewModel; -import org.tasks.ui.Toaster; - -public final class TaskListFragment extends InjectingFragment - implements OnRefreshListener, - OnMenuItemClickListener, - OnActionExpandListener, - OnQueryTextListener, - Callback { - - public static final String TAGS_METADATA_JOIN = "for_tags"; // $NON-NLS-1$ - public static final String GTASK_METADATA_JOIN = "googletask"; // $NON-NLS-1$ - public static final String CALDAV_METADATA_JOIN = "for_caldav"; // $NON-NLS-1$ - public static final String ACTION_RELOAD = "action_reload"; - public static final String ACTION_DELETED = "action_deleted"; - private static final String EXTRA_SELECTED_TASK_IDS = "extra_selected_task_ids"; - private static final String EXTRA_SEARCH = "extra_search"; - private static final int VOICE_RECOGNITION_REQUEST_CODE = 1234; - private static final String EXTRA_FILTER = "extra_filter"; - private static final String FRAG_TAG_REMOTE_LIST_PICKER = "frag_tag_remote_list_picker"; - private static final String FRAG_TAG_SORT_DIALOG = "frag_tag_sort_dialog"; - private static final String FRAG_TAG_DATE_TIME_PICKER = "frag_tag_date_time_picker"; - private static final int REQUEST_LIST_SETTINGS = 10101; - private static final int REQUEST_MOVE_TASKS = 10103; - private static final int REQUEST_TAG_TASKS = 10106; - - private static final int SEARCH_DEBOUNCE_TIMEOUT = 300; - private final RefreshReceiver refreshReceiver = new RefreshReceiver(); - private CompositeDisposable disposables; - @Inject SyncAdapters syncAdapters; - @Inject TaskDeleter taskDeleter; - @Inject @ForActivity Context context; - @Inject Preferences preferences; - @Inject DialogBuilder dialogBuilder; - @Inject TaskCreator taskCreator; - @Inject TimerPlugin timerPlugin; - @Inject ViewHolderFactory viewHolderFactory; - @Inject LocalBroadcastManager localBroadcastManager; - @Inject Device device; - @Inject TaskMover taskMover; - @Inject Toaster toaster; - @Inject TaskAdapterProvider taskAdapterProvider; - @Inject TaskDao taskDao; - @Inject TaskDuplicator taskDuplicator; - @Inject TagDataDao tagDataDao; - @Inject CaldavDao caldavDao; - @Inject ThemeColor defaultThemeColor; - @Inject ColorProvider colorProvider; - @Inject NotificationManager notificationManager; - @Inject ShortcutManager shortcutManager; - - @BindView(R.id.swipe_layout) - SwipeRefreshLayout swipeRefreshLayout; - - @BindView(R.id.swipe_layout_empty) - SwipeRefreshLayout emptyRefreshLayout; - - @BindView(R.id.toolbar) - Toolbar toolbar; - - @BindView(R.id.task_list_coordinator) - CoordinatorLayout coordinatorLayout; - - @BindView(R.id.recycler_view) - RecyclerView recyclerView; - - private TaskListViewModel taskListViewModel; - private TaskAdapter taskAdapter = null; - private TaskListRecyclerAdapter recyclerAdapter; - private Filter filter; - private final PublishSubject searchSubject = PublishSubject.create(); - private Disposable searchDisposable; - private MenuItem search; - private String searchQuery; - private ActionMode mode = null; - private ThemeColor themeColor; - - private TaskListFragmentCallbackHandler callbacks; - - static TaskListFragment newTaskListFragment(Context context, Filter filter) { - TaskListFragment fragment = new TaskListFragment(); - Bundle bundle = new Bundle(); - bundle.putParcelable( - EXTRA_FILTER, - filter == null ? BuiltInFilterExposer.getMyTasksFilter(context.getResources()) : filter); - fragment.setArguments(bundle); - return fragment; - } - - @Override - public void onRefresh() { - disposables.add( - syncAdapters - .sync(true) - .doOnSuccess( - initiated -> { - if (!initiated) { - refresh(); - } - }) - .delay(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread()) - .subscribe( - initiated -> { - if (initiated) { - setSyncOngoing(); - } - })); - } - - private void setSyncOngoing() { - assertMainThread(); - - boolean ongoing = preferences.isSyncOngoing(); - - swipeRefreshLayout.setRefreshing(ongoing); - emptyRefreshLayout.setRefreshing(ongoing); - } - - @Override - public void onViewStateRestored(@Nullable Bundle savedInstanceState) { - super.onViewStateRestored(savedInstanceState); - - if (savedInstanceState != null) { - long[] longArray = savedInstanceState.getLongArray(EXTRA_SELECTED_TASK_IDS); - if (longArray != null && longArray.length > 0) { - taskAdapter.setSelected(longArray); - startActionMode(); - } - } - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - - callbacks = (TaskListFragmentCallbackHandler) activity; - } - - @Override - public void inject(FragmentComponent component) { - component.inject(this); - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - - List selectedTaskIds = taskAdapter.getSelected(); - outState.putLongArray(EXTRA_SELECTED_TASK_IDS, Longs.toArray(selectedTaskIds)); - outState.putString(EXTRA_SEARCH, searchQuery); - } - - @Override - public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View parent = inflater.inflate(R.layout.fragment_task_list, container, false); - ButterKnife.bind(this, parent); - - filter = getFilter(); - - themeColor = filter.tint != 0 ? colorProvider.getThemeColor(filter.tint, true) : defaultThemeColor; - - filter.setFilterQueryOverride(null); - - // set up list adapters - taskAdapter = taskAdapterProvider.createTaskAdapter(filter); - - taskListViewModel = new ViewModelProvider(getActivity()).get(TaskListViewModel.class); - - if (savedInstanceState != null) { - searchQuery = savedInstanceState.getString(EXTRA_SEARCH); - } - - taskListViewModel.setFilter(searchQuery == null ? filter : createSearchFilter(searchQuery)); - - ((DefaultItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false); - recyclerView.setLayoutManager(new LinearLayoutManager(context)); - - taskListViewModel.observe( - this, - list -> { - submitList(list); - - if (list.isEmpty()) { - swipeRefreshLayout.setVisibility(View.GONE); - emptyRefreshLayout.setVisibility(View.VISIBLE); - } else { - swipeRefreshLayout.setVisibility(View.VISIBLE); - emptyRefreshLayout.setVisibility(View.GONE); - } - }); - - setupRefresh(swipeRefreshLayout); - setupRefresh(emptyRefreshLayout); - - toolbar.setTitle(filter.listingTitle); - toolbar.setNavigationIcon(R.drawable.ic_outline_menu_24px); - toolbar.setNavigationOnClickListener(v -> callbacks.onNavigationIconClicked()); - toolbar.setOnMenuItemClickListener(this); - setupMenu(); - - return parent; - } - - private void submitList(List tasks) { - if (tasks instanceof PagedList) { - if (!(recyclerAdapter instanceof PagedListRecyclerAdapter)) { - setAdapter( - new PagedListRecyclerAdapter( - taskAdapter, recyclerView, viewHolderFactory, this, tasks, taskDao)); - return; - } - } else if (!(recyclerAdapter instanceof DragAndDropRecyclerAdapter)) { - setAdapter( - new DragAndDropRecyclerAdapter( - taskAdapter, recyclerView, viewHolderFactory, this, tasks, taskDao)); - return; - } - - recyclerAdapter.submitList(tasks); - } - - private void setAdapter(TaskListRecyclerAdapter adapter) { - recyclerAdapter = adapter; - recyclerView.setAdapter(adapter); - taskAdapter.setHelper(adapter); - } - - private void setupMenu() { - Menu menu = toolbar.getMenu(); - menu.clear(); - if (filter.hasBeginningMenu()) { - toolbar.inflateMenu(filter.getBeginningMenu()); - } - toolbar.inflateMenu(R.menu.menu_task_list_fragment); - if (filter.hasMenu()) { - toolbar.inflateMenu(filter.getMenu()); - } - MenuItem hidden = menu.findItem(R.id.menu_show_hidden); - MenuItem completed = menu.findItem(R.id.menu_show_completed); - if (!taskAdapter.supportsHiddenTasks() || !filter.supportsHiddenTasks()) { - completed.setChecked(true); - completed.setEnabled(false); - hidden.setChecked(true); - hidden.setEnabled(false); - } else { - hidden.setChecked(preferences.getBoolean(R.string.p_show_hidden_tasks, false)); - completed.setChecked(preferences.getBoolean(R.string.p_show_completed_tasks, false)); - } - MenuItem sortMenu = menu.findItem(R.id.menu_sort); - if (!filter.supportsSorting()) { - sortMenu.setEnabled(false); - sortMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - } - if (preferences.disableSubtasks() - || !filter.supportSubtasks() - || taskAdapter.supportsManualSorting()) { - menu.findItem(R.id.menu_collapse_subtasks).setVisible(false); - menu.findItem(R.id.menu_expand_subtasks).setVisible(false); - } - menu.findItem(R.id.menu_voice_add).setVisible(device.voiceInputAvailable()); - - search = menu.findItem(R.id.menu_search).setOnActionExpandListener(this); - ((SearchView) search.getActionView()).setOnQueryTextListener(this); - - themeColor.apply(toolbar); - } - - private void openFilter(@Nullable Filter filter) { - if (filter == null) { - startActivity(TaskIntents.getTaskListByIdIntent(context, null)); - } else { - startActivity(TaskIntents.getTaskListIntent(context, filter)); - } - } - - private void searchByQuery(@Nullable String query) { - searchQuery = query == null ? "" : query.trim(); - if (searchQuery.isEmpty()) { - taskListViewModel.searchByFilter( - BuiltInFilterExposer.getMyTasksFilter(context.getResources())); - } else { - Filter savedFilter = createSearchFilter(searchQuery); - taskListViewModel.searchByFilter(savedFilter); - } - } - - private Filter createSearchFilter(String query) { - return new SearchFilter(getString(R.string.FLA_search_filter, query), query); - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_voice_add: - Intent recognition = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); - recognition.putExtra( - RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); - recognition.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1); - recognition.putExtra( - RecognizerIntent.EXTRA_PROMPT, getString(R.string.voice_create_prompt)); - startActivityForResult(recognition, TaskListFragment.VOICE_RECOGNITION_REQUEST_CODE); - return true; - case R.id.menu_sort: - SortDialog.newSortDialog(filter.supportsManualSort()) - .show(getChildFragmentManager(), FRAG_TAG_SORT_DIALOG); - return true; - case R.id.menu_show_hidden: - item.setChecked(!item.isChecked()); - preferences.setBoolean(R.string.p_show_hidden_tasks, item.isChecked()); - loadTaskListContent(); - localBroadcastManager.broadcastRefresh(); - return true; - case R.id.menu_show_completed: - item.setChecked(!item.isChecked()); - preferences.setBoolean(R.string.p_show_completed_tasks, item.isChecked()); - loadTaskListContent(); - localBroadcastManager.broadcastRefresh(); - return true; - case R.id.menu_clear_completed: - dialogBuilder - .newDialog(R.string.clear_completed_tasks_confirmation) - .setPositiveButton(android.R.string.ok, (dialog, which) -> clearCompleted()) - .setNegativeButton(android.R.string.cancel, null) - .show(); - return true; - case R.id.menu_filter_settings: - Intent filterSettings = new Intent(getActivity(), FilterSettingsActivity.class); - filterSettings.putExtra(FilterSettingsActivity.TOKEN_FILTER, filter); - startActivityForResult(filterSettings, REQUEST_LIST_SETTINGS); - return true; - case R.id.menu_caldav_list_fragment: - CaldavCalendar calendar = ((CaldavFilter) filter).getCalendar(); - CaldavAccount account = caldavDao.getAccountByUuid(calendar.getAccount()); - Intent caldavSettings = - new Intent( - getActivity(), - account.isCaldavAccount() - ? CaldavCalendarSettingsActivity.class - : EteSyncCalendarSettingsActivity.class); - caldavSettings.putExtra(EXTRA_CALDAV_CALENDAR, calendar); - startActivityForResult(caldavSettings, REQUEST_LIST_SETTINGS); - return true; - case R.id.menu_location_settings: - Place place = ((PlaceFilter) filter).getPlace(); - Intent intent = - new Intent( - getActivity(), - PlaceSettingsActivity.class); - intent.putExtra(PlaceSettingsActivity.EXTRA_PLACE, (Parcelable) place); - startActivityForResult(intent, REQUEST_LIST_SETTINGS); - return true; - case R.id.menu_gtasks_list_settings: - Intent gtasksSettings = new Intent(getActivity(), GoogleTaskListSettingsActivity.class); - gtasksSettings.putExtra( - GoogleTaskListSettingsActivity.EXTRA_STORE_DATA, ((GtasksFilter) filter).getList()); - startActivityForResult(gtasksSettings, REQUEST_LIST_SETTINGS); - return true; - case R.id.menu_tag_settings: - Intent tagSettings = new Intent(getActivity(), TagSettingsActivity.class); - tagSettings.putExtra(TagSettingsActivity.EXTRA_TAG_DATA, ((TagFilter) filter).getTagData()); - startActivityForResult(tagSettings, REQUEST_LIST_SETTINGS); - return true; - case R.id.menu_expand_subtasks: - taskDao.setCollapsed(taskListViewModel.getValue(), false); - localBroadcastManager.broadcastRefresh(); - return true; - case R.id.menu_collapse_subtasks: - taskDao.setCollapsed(taskListViewModel.getValue(), true); - localBroadcastManager.broadcastRefresh(); - return true; - case R.id.menu_open_map: - ((PlaceFilter) filter).openMap(context); - return true; - case R.id.menu_share: - send(taskDao.fetchTasks(preferences, filter)); - return true; - default: - return onOptionsItemSelected(item); - } - } - - private void clearCompleted() { - disposables.add( - Single.fromCallable(() -> taskDeleter.clearCompleted(filter)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - count -> toaster.longToast(R.string.delete_multiple_tasks_confirmation, count))); - } - - @OnClick(R.id.fab) - void createNewTask() { - shortcutManager.reportShortcutUsed(ShortcutManager.SHORTCUT_NEW_TASK); - onTaskListItemClicked(addTask("")); - } - - private Task addTask(String title) { - return taskCreator.createWithValues(filter, title); - } - - private void setupRefresh(SwipeRefreshLayout layout) { - layout.setOnRefreshListener(this); - layout.setColorSchemeColors( - colorProvider.getPriorityColor(0, true), - colorProvider.getPriorityColor(1, true), - colorProvider.getPriorityColor(2, true), - colorProvider.getPriorityColor(3, true)); - } - - @Override - public void onResume() { - super.onResume(); - - disposables = new CompositeDisposable(); - - localBroadcastManager.registerRefreshReceiver(refreshReceiver); - - refresh(); - } - - public Snackbar makeSnackbar(@StringRes int res, Object... args) { - return makeSnackbar(getString(res, args)); - } - - private Snackbar makeSnackbar(String text) { - Snackbar snackbar = - Snackbar.make(coordinatorLayout, text, 8000) - .setTextColor(context.getColor(R.color.snackbar_text_color)) - .setActionTextColor(context.getColor(R.color.snackbar_action_color)); - snackbar.getView().setBackgroundColor(context.getColor(R.color.snackbar_background)); - return snackbar; - } - - @Override - public void onPause() { - super.onPause(); - - disposables.dispose(); - - localBroadcastManager.unregisterReceiver(refreshReceiver); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - if (searchDisposable != null && !searchDisposable.isDisposed()) { - searchDisposable.dispose(); - } - } - - boolean collapseSearchView() { - return search.isActionViewExpanded() - && (search.collapseActionView() || !search.isActionViewExpanded()); - } - - private void refresh() { - loadTaskListContent(); - - setSyncOngoing(); - } - - public void loadTaskListContent() { - taskListViewModel.invalidate(); - } - - public Filter getFilter() { - return getArguments().getParcelable(EXTRA_FILTER); - } - - private void onTaskCreated(List tasks) { - for (Task task : tasks) { - onTaskCreated(task.getUuid()); - } - syncAdapters.sync(); - loadTaskListContent(); - } - - void onTaskCreated(String uuid) { - taskAdapter.onTaskCreated(uuid); - } - - private void onTaskDelete(Task task) { - MainActivity activity = (MainActivity) getActivity(); - if (activity != null) { - TaskEditFragment tef = activity.getTaskEditFragment(); - if (tef != null && task.getId() == tef.model.getId()) { - tef.discard(); - } - } - timerPlugin.stopTimer(task); - taskAdapter.onTaskDeleted(task); - loadTaskListContent(); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case VOICE_RECOGNITION_REQUEST_CODE: - if (resultCode == RESULT_OK) { - List match = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); - if (match != null && match.size() > 0 && match.get(0).length() > 0) { - String recognizedSpeech = match.get(0); - recognizedSpeech = - recognizedSpeech.substring(0, 1).toUpperCase() - + recognizedSpeech.substring(1).toLowerCase(); - - onTaskListItemClicked(addTask(recognizedSpeech)); - } - } - break; - case REQUEST_MOVE_TASKS: - if (resultCode == RESULT_OK) { - taskMover.move( - taskAdapter.getSelected(), - data.getParcelableExtra(RemoteListPicker.EXTRA_SELECTED_FILTER)); - finishActionMode(); - } - break; - case REQUEST_LIST_SETTINGS: - if (resultCode == Activity.RESULT_OK) { - String action = data.getAction(); - if (ACTION_DELETED.equals(action)) { - openFilter(null); - } else if (ACTION_RELOAD.equals(action)) { - openFilter(data.getParcelableExtra(MainActivity.OPEN_FILTER)); - } - } - break; - case REQUEST_TAG_TASKS: - if (resultCode == RESULT_OK) { - List modified = - tagDataDao.applyTags( - taskDao.fetch( - (ArrayList) data.getSerializableExtra(TagPickerActivity.EXTRA_TASKS)), - data.getParcelableArrayListExtra(TagPickerActivity.EXTRA_PARTIALLY_SELECTED), - data.getParcelableArrayListExtra(TagPickerActivity.EXTRA_SELECTED)); - taskDao.touch(modified); - finishActionMode(); - } - break; - default: - super.onActivityResult(requestCode, resultCode, data); - } - } - - @Override - public boolean onContextItemSelected(android.view.MenuItem item) { - return onOptionsItemSelected(item); - } - - public void onTaskListItemClicked(Task task) { - callbacks.onTaskListItemClicked(task); - } - - @Override - public boolean onMenuItemActionExpand(MenuItem item) { - searchDisposable = - searchSubject - .debounce(SEARCH_DEBOUNCE_TIMEOUT, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::searchByQuery); - if (searchQuery == null) { - searchByQuery(""); - } - Menu menu = toolbar.getMenu(); - for (int i = 0; i < menu.size(); i++) { - menu.getItem(i).setVisible(false); - } - return true; - } - - @Override - public boolean onMenuItemActionCollapse(MenuItem item) { - taskListViewModel.searchByFilter(filter); - searchDisposable.dispose(); - searchQuery = null; - setupMenu(); - return true; - } - - @Override - public boolean onQueryTextSubmit(String query) { - openFilter(createSearchFilter(query.trim())); - search.collapseActionView(); - return true; - } - - @Override - public boolean onQueryTextChange(String query) { - searchSubject.onNext(query); - return true; - } - - public void broadcastRefresh() { - localBroadcastManager.broadcastRefresh(); - } - - @Override - public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { - MenuInflater inflater = actionMode.getMenuInflater(); - inflater.inflate(R.menu.menu_multi_select, menu); - themeColor.colorMenu(menu); - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - ArrayList selected = taskAdapter.getSelected(); - switch (item.getItemId()) { - case R.id.edit_tags: - Pair, Set> tags = tagDataDao.getTagSelections(selected); - Intent intent = new Intent(context, TagPickerActivity.class); - intent.putExtra(TagPickerActivity.EXTRA_TASKS, selected); - intent.putParcelableArrayListExtra( - TagPickerActivity.EXTRA_PARTIALLY_SELECTED, - new ArrayList<>(tagDataDao.getByUuid(tags.first))); - intent.putParcelableArrayListExtra( - TagPickerActivity.EXTRA_SELECTED, new ArrayList<>(tagDataDao.getByUuid(tags.second))); - startActivityForResult(intent, REQUEST_TAG_TASKS); - return true; - case R.id.move_tasks: - Filter singleFilter = taskMover.getSingleFilter(selected); - (singleFilter == null - ? newRemoteListSupportPicker(this, REQUEST_MOVE_TASKS) - : newRemoteListSupportPicker(singleFilter, this, REQUEST_MOVE_TASKS)) - .show(getParentFragmentManager(), FRAG_TAG_REMOTE_LIST_PICKER); - return true; - case R.id.menu_select_all: - taskAdapter.setSelected( - transform(taskDao.fetchTasks(preferences, filter), TaskContainer::getId)); - updateModeTitle(); - recyclerAdapter.notifyDataSetChanged(); - return true; - case R.id.menu_share: - send( - collect( - taskAdapter.getSelected(), - ids -> taskDao.fetchTasks(preferences, new IdListFilter(ids)))); - return true; - case R.id.delete: - dialogBuilder - .newDialog(R.string.delete_selected_tasks) - .setPositiveButton( - android.R.string.ok, (dialogInterface, i) -> deleteSelectedItems(selected)) - .setNegativeButton(android.R.string.cancel, null) - .show(); - return true; - case R.id.copy_tasks: - dialogBuilder - .newDialog(R.string.copy_selected_tasks) - .setPositiveButton( - android.R.string.ok, ((dialogInterface, i) -> copySelectedItems(selected))) - .setNegativeButton(android.R.string.cancel, null) - .show(); - return true; - default: - return false; - } - } - - private void send(List tasks) { - Intent intent = new Intent(Intent.ACTION_SEND); - String output = - Joiner.on("\n") - .join( - transform( - tasks, t -> { - String checkbox = t.isCompleted() ? "☑" : "☐"; - checkbox = padStart(checkbox, 1 + t.getIndent() * 3, ' '); - return String.format("%s %s", checkbox, t.getTitle()); - })); - intent.putExtra(Intent.EXTRA_SUBJECT, filter.listingTitle); - intent.putExtra(Intent.EXTRA_TEXT, output); - intent.setType("text/plain"); - - startActivity(Intent.createChooser(intent, null)); - - finishActionMode(); - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - this.mode = null; - if (taskAdapter.getNumSelected() > 0) { - taskAdapter.clearSelections(); - recyclerAdapter.notifyDataSetChanged(); - } - } - - public void showDateTimePicker(TaskContainer task) { - FragmentManager fragmentManager = getParentFragmentManager(); - if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_TIME_PICKER) == null) { - DateTimePicker.Companion.newDateTimePicker( - task.getId(), - task.getDueDate(), - preferences.getBoolean(R.string.p_auto_dismiss_datetime_list_screen, false)) - .show(fragmentManager, FRAG_TAG_DATE_TIME_PICKER); - } - } - - public interface TaskListFragmentCallbackHandler { - void onTaskListItemClicked(Task task); - - void onNavigationIconClicked(); - } - - protected class RefreshReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - refresh(); - } - } - - public boolean isActionModeActive() { - return mode != null; - } - - public void startActionMode() { - if (mode == null) { - mode = ((AppCompatActivity) getActivity()).startSupportActionMode(this); - updateModeTitle(); - if (taskAdapter.supportsParentingOrManualSort()) { - Flags.set(Flags.TLFP_NO_INTERCEPT_TOUCH); - } - } - } - - public void finishActionMode() { - if (mode != null) { - mode.finish(); - } - } - - public void updateModeTitle() { - if (mode != null) { - int count = Math.max(1, taskAdapter.getNumSelected()); - mode.setTitle(Integer.toString(count)); - } - } - - private void deleteSelectedItems(List tasks) { - finishActionMode(); - List result = taskDeleter.markDeleted(tasks); - for (Task task : result) { - onTaskDelete(task); - } - makeSnackbar(R.string.delete_multiple_tasks_confirmation, Integer.toString(result.size())) - .show(); - } - - private void copySelectedItems(List tasks) { - finishActionMode(); - List duplicates = taskDuplicator.duplicate(tasks); - onTaskCreated(duplicates); - makeSnackbar(R.string.copy_multiple_tasks_confirmation, Integer.toString(duplicates.size())) - .show(); - } -} diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt new file mode 100644 index 000000000..084b86005 --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt @@ -0,0 +1,771 @@ +/* + * 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.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.os.Parcelable +import android.speech.RecognizerIntent +import android.view.* +import androidx.annotation.StringRes +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ActionMode +import androidx.appcompat.widget.SearchView +import androidx.appcompat.widget.Toolbar +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import androidx.paging.PagedList +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener +import butterknife.BindView +import butterknife.ButterKnife +import butterknife.OnClick +import com.google.android.material.snackbar.Snackbar +import com.todoroo.andlib.utility.AndroidUtilities +import com.todoroo.astrid.adapter.TaskAdapter +import com.todoroo.astrid.adapter.TaskAdapterProvider +import com.todoroo.astrid.api.* +import com.todoroo.astrid.core.BuiltInFilterExposer +import com.todoroo.astrid.dao.TaskDao +import com.todoroo.astrid.data.Task +import com.todoroo.astrid.service.TaskCreator +import com.todoroo.astrid.service.TaskDeleter +import com.todoroo.astrid.service.TaskDuplicator +import com.todoroo.astrid.service.TaskMover +import com.todoroo.astrid.timers.TimerPlugin +import com.todoroo.astrid.utility.Flags +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import io.reactivex.subjects.PublishSubject +import org.tasks.LocalBroadcastManager +import org.tasks.R +import org.tasks.ShortcutManager +import org.tasks.activities.* +import org.tasks.caldav.BaseCaldavCalendarSettingsActivity +import org.tasks.caldav.CaldavCalendarSettingsActivity +import org.tasks.data.CaldavDao +import org.tasks.data.TagDataDao +import org.tasks.data.TaskContainer +import org.tasks.db.DbUtils +import org.tasks.dialogs.DateTimePicker.Companion.newDateTimePicker +import org.tasks.dialogs.DialogBuilder +import org.tasks.dialogs.SortDialog +import org.tasks.etesync.EteSyncCalendarSettingsActivity +import org.tasks.filters.PlaceFilter +import org.tasks.injection.FragmentComponent +import org.tasks.injection.InjectingFragment +import org.tasks.intents.TaskIntents +import org.tasks.notifications.NotificationManager +import org.tasks.preferences.Device +import org.tasks.preferences.Preferences +import org.tasks.sync.SyncAdapters +import org.tasks.tags.TagPickerActivity +import org.tasks.tasklist.DragAndDropRecyclerAdapter +import org.tasks.tasklist.PagedListRecyclerAdapter +import org.tasks.tasklist.TaskListRecyclerAdapter +import org.tasks.tasklist.ViewHolderFactory +import org.tasks.themes.ColorProvider +import org.tasks.themes.ThemeColor +import org.tasks.ui.TaskListViewModel +import org.tasks.ui.Toaster +import java.util.* +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import kotlin.math.max + +class TaskListFragment : InjectingFragment(), OnRefreshListener, Toolbar.OnMenuItemClickListener, MenuItem.OnActionExpandListener, SearchView.OnQueryTextListener, ActionMode.Callback { + private val refreshReceiver = RefreshReceiver() + private var disposables: CompositeDisposable? = null + + @Inject lateinit var syncAdapters: SyncAdapters + @Inject lateinit var taskDeleter: TaskDeleter + @Inject lateinit var preferences: Preferences + @Inject lateinit var dialogBuilder: DialogBuilder + @Inject lateinit var taskCreator: TaskCreator + @Inject lateinit var timerPlugin: TimerPlugin + @Inject lateinit var viewHolderFactory: ViewHolderFactory + @Inject lateinit var localBroadcastManager: LocalBroadcastManager + @Inject lateinit var device: Device + @Inject lateinit var taskMover: TaskMover + @Inject lateinit var toaster: Toaster + @Inject lateinit var taskAdapterProvider: TaskAdapterProvider + @Inject lateinit var taskDao: TaskDao + @Inject lateinit var taskDuplicator: TaskDuplicator + @Inject lateinit var tagDataDao: TagDataDao + @Inject lateinit var caldavDao: CaldavDao + @Inject lateinit var defaultThemeColor: ThemeColor + @Inject lateinit var colorProvider: ColorProvider + @Inject lateinit var notificationManager: NotificationManager + @Inject lateinit var shortcutManager: ShortcutManager + + @BindView(R.id.swipe_layout) + lateinit var swipeRefreshLayout: SwipeRefreshLayout + + @BindView(R.id.swipe_layout_empty) + lateinit var emptyRefreshLayout: SwipeRefreshLayout + + @BindView(R.id.toolbar) + lateinit var toolbar: Toolbar + + @BindView(R.id.task_list_coordinator) + lateinit var coordinatorLayout: CoordinatorLayout + + @BindView(R.id.recycler_view) + lateinit var recyclerView: RecyclerView + + private lateinit var taskListViewModel: TaskListViewModel + private lateinit var taskAdapter: TaskAdapter + private var recyclerAdapter: TaskListRecyclerAdapter? = null + private lateinit var filter: Filter + private val searchSubject = PublishSubject.create() + private var searchDisposable: Disposable? = null + private lateinit var search: MenuItem + private var searchQuery: String? = null + private var mode: ActionMode? = null + private lateinit var themeColor: ThemeColor + private lateinit var callbacks: TaskListFragmentCallbackHandler + + override fun onRefresh() { + disposables!!.add( + syncAdapters + .sync(true) + .doOnSuccess { initiated: Boolean -> + if (!initiated) { + refresh() + } + } + .delay(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread()) + .subscribe { initiated: Boolean -> + if (initiated) { + setSyncOngoing() + } + }) + } + + private fun setSyncOngoing() { + AndroidUtilities.assertMainThread() + val ongoing = preferences.isSyncOngoing + swipeRefreshLayout.isRefreshing = ongoing + emptyRefreshLayout.isRefreshing = ongoing + } + + override fun onViewStateRestored(savedInstanceState: Bundle?) { + super.onViewStateRestored(savedInstanceState) + if (savedInstanceState != null) { + val longArray = savedInstanceState.getLongArray(EXTRA_SELECTED_TASK_IDS) + if (longArray?.isNotEmpty() == true) { + taskAdapter.setSelected(*longArray) + startActionMode() + } + } + } + + override fun onAttach(activity: Activity) { + super.onAttach(activity) + callbacks = activity as TaskListFragmentCallbackHandler + } + + public override fun inject(component: FragmentComponent) = component.inject(this) + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + val selectedTaskIds: List = taskAdapter.selected + outState.putLongArray(EXTRA_SELECTED_TASK_IDS, selectedTaskIds.toLongArray()) + outState.putString(EXTRA_SEARCH, searchQuery) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val parent = inflater.inflate(R.layout.fragment_task_list, container, false) + ButterKnife.bind(this, parent) + filter = getFilter() + themeColor = if (filter.tint != 0) colorProvider.getThemeColor(filter.tint, true) else defaultThemeColor + filter.setFilterQueryOverride(null) + + // set up list adapters + taskAdapter = taskAdapterProvider.createTaskAdapter(filter) + taskListViewModel = ViewModelProvider(requireActivity()).get(TaskListViewModel::class.java) + if (savedInstanceState != null) { + searchQuery = savedInstanceState.getString(EXTRA_SEARCH) + } + taskListViewModel.setFilter((if (searchQuery == null) filter else createSearchFilter(searchQuery!!))) + (recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false + recyclerView.layoutManager = LinearLayoutManager(context) + taskListViewModel.observe( + this, + Observer { list: List -> + submitList(list) + if (list.isEmpty()) { + swipeRefreshLayout.visibility = View.GONE + emptyRefreshLayout.visibility = View.VISIBLE + } else { + swipeRefreshLayout.visibility = View.VISIBLE + emptyRefreshLayout.visibility = View.GONE + } + }) + setupRefresh(swipeRefreshLayout) + setupRefresh(emptyRefreshLayout) + toolbar.title = filter.listingTitle + toolbar.setNavigationIcon(R.drawable.ic_outline_menu_24px) + toolbar.setNavigationOnClickListener { callbacks.onNavigationIconClicked() } + toolbar.setOnMenuItemClickListener(this) + setupMenu() + return parent + } + + private fun submitList(tasks: List) { + if (tasks is PagedList<*>) { + if (recyclerAdapter !is PagedListRecyclerAdapter) { + setAdapter( + PagedListRecyclerAdapter( + taskAdapter, recyclerView, viewHolderFactory, this, tasks, taskDao)) + return + } + } else if (recyclerAdapter !is DragAndDropRecyclerAdapter) { + setAdapter( + DragAndDropRecyclerAdapter( + taskAdapter, recyclerView, viewHolderFactory, this, tasks, taskDao)) + return + } + recyclerAdapter!!.submitList(tasks) + } + + private fun setAdapter(adapter: TaskListRecyclerAdapter) { + recyclerAdapter = adapter + recyclerView.adapter = adapter + taskAdapter.setHelper(adapter) + } + + private fun setupMenu() { + val menu = toolbar.menu + menu.clear() + if (filter.hasBeginningMenu()) { + toolbar.inflateMenu(filter.beginningMenu) + } + toolbar.inflateMenu(R.menu.menu_task_list_fragment) + if (filter.hasMenu()) { + toolbar.inflateMenu(filter.menu) + } + val hidden = menu.findItem(R.id.menu_show_hidden) + val completed = menu.findItem(R.id.menu_show_completed) + if (!taskAdapter.supportsHiddenTasks() || !filter.supportsHiddenTasks()) { + completed.isChecked = true + completed.isEnabled = false + hidden.isChecked = true + hidden.isEnabled = false + } else { + hidden.isChecked = preferences.getBoolean(R.string.p_show_hidden_tasks, false) + completed.isChecked = preferences.getBoolean(R.string.p_show_completed_tasks, false) + } + val sortMenu = menu.findItem(R.id.menu_sort) + if (!filter.supportsSorting()) { + sortMenu.isEnabled = false + sortMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) + } + if (preferences.disableSubtasks() + || !filter.supportSubtasks() + || taskAdapter.supportsManualSorting()) { + menu.findItem(R.id.menu_collapse_subtasks).isVisible = false + menu.findItem(R.id.menu_expand_subtasks).isVisible = false + } + menu.findItem(R.id.menu_voice_add).isVisible = device.voiceInputAvailable() + search = menu.findItem(R.id.menu_search).setOnActionExpandListener(this) + (search.actionView as SearchView).setOnQueryTextListener(this) + themeColor.apply(toolbar) + } + + private fun openFilter(filter: Filter?) { + if (filter == null) { + startActivity(TaskIntents.getTaskListByIdIntent(context, null)) + } else { + startActivity(TaskIntents.getTaskListIntent(context, filter)) + } + } + + private fun searchByQuery(query: String?) { + searchQuery = query?.trim { it <= ' ' } ?: "" + if (searchQuery?.isEmpty() == true) { + taskListViewModel.searchByFilter( + BuiltInFilterExposer.getMyTasksFilter(requireContext().resources)) + } else { + val savedFilter = createSearchFilter(searchQuery!!) + taskListViewModel.searchByFilter(savedFilter) + } + } + + private fun createSearchFilter(query: String): Filter { + return SearchFilter(getString(R.string.FLA_search_filter, query), query) + } + + override fun onMenuItemClick(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.menu_voice_add -> { + val recognition = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) + recognition.putExtra( + RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) + recognition.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1) + recognition.putExtra( + RecognizerIntent.EXTRA_PROMPT, getString(R.string.voice_create_prompt)) + startActivityForResult(recognition, VOICE_RECOGNITION_REQUEST_CODE) + true + } + R.id.menu_sort -> { + SortDialog.newSortDialog(filter.supportsManualSort()) + .show(childFragmentManager, FRAG_TAG_SORT_DIALOG) + true + } + R.id.menu_show_hidden -> { + item.isChecked = !item.isChecked + preferences.setBoolean(R.string.p_show_hidden_tasks, item.isChecked) + loadTaskListContent() + localBroadcastManager.broadcastRefresh() + true + } + R.id.menu_show_completed -> { + item.isChecked = !item.isChecked + preferences.setBoolean(R.string.p_show_completed_tasks, item.isChecked) + loadTaskListContent() + localBroadcastManager.broadcastRefresh() + true + } + R.id.menu_clear_completed -> { + dialogBuilder + .newDialog(R.string.clear_completed_tasks_confirmation) + .setPositiveButton(android.R.string.ok) { _, _ -> clearCompleted() } + .setNegativeButton(android.R.string.cancel, null) + .show() + true + } + R.id.menu_filter_settings -> { + val filterSettings = Intent(activity, FilterSettingsActivity::class.java) + filterSettings.putExtra(FilterSettingsActivity.TOKEN_FILTER, filter) + startActivityForResult(filterSettings, REQUEST_LIST_SETTINGS) + true + } + R.id.menu_caldav_list_fragment -> { + val calendar = (filter as CaldavFilter).calendar + val account = caldavDao.getAccountByUuid(calendar.account!!) + val caldavSettings = Intent( + activity, + if (account!!.isCaldavAccount) CaldavCalendarSettingsActivity::class.java else EteSyncCalendarSettingsActivity::class.java) + caldavSettings.putExtra(BaseCaldavCalendarSettingsActivity.EXTRA_CALDAV_CALENDAR, calendar) + startActivityForResult(caldavSettings, REQUEST_LIST_SETTINGS) + true + } + R.id.menu_location_settings -> { + val place = (filter as PlaceFilter).place + val intent = Intent(activity, PlaceSettingsActivity::class.java) + intent.putExtra(PlaceSettingsActivity.EXTRA_PLACE, place as Parcelable) + startActivityForResult(intent, REQUEST_LIST_SETTINGS) + true + } + R.id.menu_gtasks_list_settings -> { + val gtasksSettings = Intent(activity, GoogleTaskListSettingsActivity::class.java) + gtasksSettings.putExtra( + GoogleTaskListSettingsActivity.EXTRA_STORE_DATA, (filter as GtasksFilter).list) + startActivityForResult(gtasksSettings, REQUEST_LIST_SETTINGS) + true + } + R.id.menu_tag_settings -> { + val tagSettings = Intent(activity, TagSettingsActivity::class.java) + tagSettings.putExtra(TagSettingsActivity.EXTRA_TAG_DATA, (filter as TagFilter).tagData) + startActivityForResult(tagSettings, REQUEST_LIST_SETTINGS) + true + } + R.id.menu_expand_subtasks -> { + taskDao.setCollapsed(taskListViewModel.value, false) + localBroadcastManager.broadcastRefresh() + true + } + R.id.menu_collapse_subtasks -> { + taskDao.setCollapsed(taskListViewModel.value, true) + localBroadcastManager.broadcastRefresh() + true + } + R.id.menu_open_map -> { + (filter as PlaceFilter).openMap(context) + true + } + R.id.menu_share -> { + send(taskDao.fetchTasks(preferences, filter)) + true + } + else -> onOptionsItemSelected(item) + } + } + + private fun clearCompleted() { + disposables!!.add( + Single.fromCallable { taskDeleter.clearCompleted(filter) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { count: Int -> toaster.longToast(R.string.delete_multiple_tasks_confirmation, count) }) + } + + @OnClick(R.id.fab) + fun createNewTask() { + shortcutManager.reportShortcutUsed(ShortcutManager.SHORTCUT_NEW_TASK) + onTaskListItemClicked(addTask("")) + } + + private fun addTask(title: String): Task { + return taskCreator.createWithValues(filter, title) + } + + private fun setupRefresh(layout: SwipeRefreshLayout) { + layout.setOnRefreshListener(this) + layout.setColorSchemeColors( + colorProvider.getPriorityColor(0, true), + colorProvider.getPriorityColor(1, true), + colorProvider.getPriorityColor(2, true), + colorProvider.getPriorityColor(3, true)) + } + + override fun onResume() { + super.onResume() + disposables = CompositeDisposable() + localBroadcastManager.registerRefreshReceiver(refreshReceiver) + refresh() + } + + fun makeSnackbar(@StringRes res: Int, vararg args: Any?): Snackbar { + return makeSnackbar(getString(res, *args)) + } + + private fun makeSnackbar(text: String): Snackbar { + val snackbar = Snackbar.make(coordinatorLayout, text, 8000) + .setTextColor(requireActivity().getColor(R.color.snackbar_text_color)) + .setActionTextColor(requireActivity().getColor(R.color.snackbar_action_color)) + snackbar.view.setBackgroundColor(requireActivity().getColor(R.color.snackbar_background)) + return snackbar + } + + override fun onPause() { + super.onPause() + disposables?.dispose() + localBroadcastManager.unregisterReceiver(refreshReceiver) + } + + override fun onDestroyView() { + super.onDestroyView() + if (searchDisposable != null && !searchDisposable!!.isDisposed) { + searchDisposable!!.dispose() + } + } + + fun collapseSearchView(): Boolean { + return (search.isActionViewExpanded + && (search.collapseActionView() || !search.isActionViewExpanded)) + } + + private fun refresh() { + loadTaskListContent() + setSyncOngoing() + } + + fun loadTaskListContent() { + taskListViewModel.invalidate() + } + + fun getFilter(): Filter { + return requireArguments().getParcelable(EXTRA_FILTER)!! + } + + private fun onTaskCreated(tasks: List) { + for (task in tasks) { + onTaskCreated(task.uuid) + } + syncAdapters.sync() + loadTaskListContent() + } + + fun onTaskCreated(uuid: String?) { + taskAdapter.onTaskCreated(uuid) + } + + private fun onTaskDelete(task: Task) { + val activity = activity as MainActivity? + if (activity != null) { + val tef = activity.taskEditFragment + if (tef != null && task.id == tef.model.id) { + tef.discard() + } + } + timerPlugin.stopTimer(task) + taskAdapter.onTaskDeleted(task) + loadTaskListContent() + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + when (requestCode) { + VOICE_RECOGNITION_REQUEST_CODE -> if (resultCode == Activity.RESULT_OK) { + val match: List? = data!!.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS) + if (match != null && match.isNotEmpty() && match[0].isNotEmpty()) { + var recognizedSpeech = match[0] + recognizedSpeech = (recognizedSpeech.substring(0, 1).toUpperCase() + + recognizedSpeech.substring(1).toLowerCase()) + onTaskListItemClicked(addTask(recognizedSpeech)) + } + } + REQUEST_MOVE_TASKS -> if (resultCode == Activity.RESULT_OK) { + taskMover.move( + taskAdapter.selected, + data!!.getParcelableExtra(RemoteListPicker.EXTRA_SELECTED_FILTER)) + finishActionMode() + } + REQUEST_LIST_SETTINGS -> if (resultCode == Activity.RESULT_OK) { + val action = data!!.action + if (ACTION_DELETED == action) { + openFilter(null) + } else if (ACTION_RELOAD == action) { + openFilter(data.getParcelableExtra(MainActivity.OPEN_FILTER)) + } + } + REQUEST_TAG_TASKS -> if (resultCode == Activity.RESULT_OK) { + val modified = tagDataDao.applyTags( + taskDao.fetch( + data!!.getSerializableExtra(TagPickerActivity.EXTRA_TASKS) as ArrayList), + data.getParcelableArrayListExtra(TagPickerActivity.EXTRA_PARTIALLY_SELECTED)!!, + data.getParcelableArrayListExtra(TagPickerActivity.EXTRA_SELECTED)!!) + taskDao.touch(modified) + finishActionMode() + } + else -> super.onActivityResult(requestCode, resultCode, data) + } + } + + override fun onContextItemSelected(item: MenuItem): Boolean { + return onOptionsItemSelected(item) + } + + fun onTaskListItemClicked(task: Task?) { + callbacks.onTaskListItemClicked(task) + } + + override fun onMenuItemActionExpand(item: MenuItem): Boolean { + searchDisposable = searchSubject + .debounce(SEARCH_DEBOUNCE_TIMEOUT.toLong(), TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { query: String? -> searchByQuery(query) } + if (searchQuery == null) { + searchByQuery("") + } + val menu = toolbar.menu + for (i in 0 until menu.size()) { + menu.getItem(i).isVisible = false + } + return true + } + + override fun onMenuItemActionCollapse(item: MenuItem): Boolean { + taskListViewModel.searchByFilter(filter) + searchDisposable?.dispose() + searchQuery = null + setupMenu() + return true + } + + override fun onQueryTextSubmit(query: String): Boolean { + openFilter(createSearchFilter(query.trim { it <= ' ' })) + search.collapseActionView() + return true + } + + override fun onQueryTextChange(query: String): Boolean { + searchSubject.onNext(query) + return true + } + + fun broadcastRefresh() { + localBroadcastManager.broadcastRefresh() + } + + override fun onCreateActionMode(actionMode: ActionMode, menu: Menu): Boolean { + val inflater = actionMode.menuInflater + inflater.inflate(R.menu.menu_multi_select, menu) + themeColor.colorMenu(menu) + return true + } + + override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + return false + } + + override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { + val selected = taskAdapter.selected + return when (item.itemId) { + R.id.edit_tags -> { + val tags = tagDataDao.getTagSelections(selected) + val intent = Intent(context, TagPickerActivity::class.java) + intent.putExtra(TagPickerActivity.EXTRA_TASKS, selected) + intent.putParcelableArrayListExtra( + TagPickerActivity.EXTRA_PARTIALLY_SELECTED, + ArrayList(tagDataDao.getByUuid(tags.first!!))) + intent.putParcelableArrayListExtra( + TagPickerActivity.EXTRA_SELECTED, ArrayList(tagDataDao.getByUuid(tags.second!!))) + startActivityForResult(intent, REQUEST_TAG_TASKS) + true + } + R.id.move_tasks -> { + val singleFilter = taskMover.getSingleFilter(selected) + (if (singleFilter == null) RemoteListPicker.newRemoteListSupportPicker(this, REQUEST_MOVE_TASKS) else RemoteListPicker.newRemoteListSupportPicker(singleFilter, this, REQUEST_MOVE_TASKS)) + .show(parentFragmentManager, FRAG_TAG_REMOTE_LIST_PICKER) + true + } + R.id.menu_select_all -> { + taskAdapter.setSelected(taskDao.fetchTasks(preferences, filter).map(TaskContainer::getId)) + updateModeTitle() + recyclerAdapter!!.notifyDataSetChanged() + true + } + R.id.menu_share -> { + send( + DbUtils.collect( + taskAdapter.selected + ) { ids: List? -> taskDao.fetchTasks(preferences, IdListFilter(ids)) }) + true + } + R.id.delete -> { + dialogBuilder + .newDialog(R.string.delete_selected_tasks) + .setPositiveButton( + android.R.string.ok) { _, _ -> deleteSelectedItems(selected) } + .setNegativeButton(android.R.string.cancel, null) + .show() + true + } + R.id.copy_tasks -> { + dialogBuilder + .newDialog(R.string.copy_selected_tasks) + .setPositiveButton( + android.R.string.ok) { _, _ -> copySelectedItems(selected) } + .setNegativeButton(android.R.string.cancel, null) + .show() + true + } + else -> false + } + } + + private fun send(tasks: List) { + val intent = Intent(Intent.ACTION_SEND) + val output = tasks.joinToString("\n") { t -> Task + "${(if (t.isCompleted) "☑" else "☐").padStart(1 + t.getIndent() * 3, ' ')} ${t.title}" + } + intent.putExtra(Intent.EXTRA_SUBJECT, filter.listingTitle) + intent.putExtra(Intent.EXTRA_TEXT, output) + intent.type = "text/plain" + startActivity(Intent.createChooser(intent, null)) + finishActionMode() + } + + override fun onDestroyActionMode(mode: ActionMode) { + this.mode = null + if (taskAdapter.numSelected > 0) { + taskAdapter.clearSelections() + recyclerAdapter!!.notifyDataSetChanged() + } + } + + fun showDateTimePicker(task: TaskContainer) { + val fragmentManager = parentFragmentManager + if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_TIME_PICKER) == null) { + newDateTimePicker( + task.id, + task.dueDate, + preferences.getBoolean(R.string.p_auto_dismiss_datetime_list_screen, false)) + .show(fragmentManager, FRAG_TAG_DATE_TIME_PICKER) + } + } + + interface TaskListFragmentCallbackHandler { + fun onTaskListItemClicked(task: Task?) + fun onNavigationIconClicked() + } + + private inner class RefreshReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + refresh() + } + } + + val isActionModeActive: Boolean + get() = mode != null + + fun startActionMode() { + if (mode == null) { + mode = (activity as AppCompatActivity).startSupportActionMode(this) + updateModeTitle() + if (taskAdapter.supportsParentingOrManualSort()) { + Flags.set(Flags.TLFP_NO_INTERCEPT_TOUCH) + } + } + } + + fun finishActionMode() { + mode?.finish() + } + + fun updateModeTitle() { + if (mode != null) { + val count = max(1, taskAdapter.numSelected) + mode!!.title = count.toString() + } + } + + private fun deleteSelectedItems(tasks: List) { + finishActionMode() + + val result = taskDeleter.markDeleted(tasks) + result.forEach(this::onTaskDelete) + makeSnackbar(R.string.delete_multiple_tasks_confirmation, result.size.toString()).show() + } + + private fun copySelectedItems(tasks: List) { + finishActionMode() + val duplicates = taskDuplicator.duplicate(tasks) + onTaskCreated(duplicates) + makeSnackbar(R.string.copy_multiple_tasks_confirmation, duplicates.size.toString()).show() + } + + companion object { + const val TAGS_METADATA_JOIN = "for_tags" // $NON-NLS-1$ + const val GTASK_METADATA_JOIN = "googletask" // $NON-NLS-1$ + const val CALDAV_METADATA_JOIN = "for_caldav" // $NON-NLS-1$ + const val ACTION_RELOAD = "action_reload" + const val ACTION_DELETED = "action_deleted" + private const val EXTRA_SELECTED_TASK_IDS = "extra_selected_task_ids" + private const val EXTRA_SEARCH = "extra_search" + private const val VOICE_RECOGNITION_REQUEST_CODE = 1234 + private const val EXTRA_FILTER = "extra_filter" + private const val FRAG_TAG_REMOTE_LIST_PICKER = "frag_tag_remote_list_picker" + private const val FRAG_TAG_SORT_DIALOG = "frag_tag_sort_dialog" + private const val FRAG_TAG_DATE_TIME_PICKER = "frag_tag_date_time_picker" + private const val REQUEST_LIST_SETTINGS = 10101 + private const val REQUEST_MOVE_TASKS = 10103 + private const val REQUEST_TAG_TASKS = 10106 + private const val SEARCH_DEBOUNCE_TIMEOUT = 300 + fun newTaskListFragment(context: Context, filter: Filter?): TaskListFragment { + val fragment = TaskListFragment() + val bundle = Bundle() + bundle.putParcelable( + EXTRA_FILTER, + filter ?: BuiltInFilterExposer.getMyTasksFilter(context.resources)) + fragment.arguments = bundle + return fragment + } + } +} \ No newline at end of file