You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tasks/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt

767 lines
31 KiB
Kotlin

/*
* 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.fragment.app.Fragment
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 dagger.hilt.android.AndroidEntryPoint
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.data.CaldavDao
import org.tasks.data.TagDataDao
import org.tasks.data.TaskContainer
import org.tasks.db.DbUtils.chunkedMap
import org.tasks.dialogs.DateTimePicker.Companion.newDateTimePicker
import org.tasks.dialogs.DialogBuilder
import org.tasks.dialogs.SortDialog
import org.tasks.filters.PlaceFilter
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
@AndroidEntryPoint
class TaskListFragment : Fragment(), 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<String>()
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.toList())
startActionMode()
}
}
}
override fun onAttach(activity: Activity) {
super.onAttach(activity)
callbacks = activity as TaskListFragmentCallbackHandler
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val selectedTaskIds: List<Long> = taskAdapter.getSelected()
outState.putLongArray(EXTRA_SELECTED_TASK_IDS, selectedTaskIds.toLongArray())
outState.putString(EXTRA_SEARCH, searchQuery)
outState.putLongArray(EXTRA_COLLAPSED, taskAdapter.getCollapsed().toLongArray())
}
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)
taskAdapter.setCollapsed(savedInstanceState?.getLongArray(EXTRA_COLLAPSED))
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<TaskContainer> ->
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<TaskContainer>) {
if (tasks is PagedList<*>) {
if (recyclerAdapter !is PagedListRecyclerAdapter) {
setAdapter(
PagedListRecyclerAdapter(
taskAdapter, recyclerView, viewHolderFactory, this, tasks, taskDao, preferences))
return
}
} else if (recyclerAdapter !is DragAndDropRecyclerAdapter) {
setAdapter(
DragAndDropRecyclerAdapter(
taskAdapter, recyclerView, viewHolderFactory, this, tasks, taskDao, preferences))
return
}
recyclerAdapter!!.submitList(tasks)
}
private fun setAdapter(adapter: TaskListRecyclerAdapter) {
recyclerAdapter = adapter
recyclerView.adapter = adapter
taskAdapter.setDataSource(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.usePagedQueries()
|| !filter.supportsSubtasks()
|| taskAdapter.supportsAstridSorting()) {
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)
.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, account!!.listSettingsClass())
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(preferences, filter, false)
localBroadcastManager.broadcastRefresh()
true
}
R.id.menu_collapse_subtasks -> {
taskDao.setCollapsed(preferences, filter, 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<Task>) {
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<String>? = 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.getSelected(),
data!!.getParcelableExtra(ListPicker.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<Long>),
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.getSelected()
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) ListPicker.newListPicker(this, REQUEST_MOVE_TASKS) else ListPicker.newListPicker(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 -> {
selected.chunkedMap { taskDao.fetchTasks(preferences, IdListFilter(it)) }
.apply(this::send)
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<TaskContainer>) {
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()
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<Long>) {
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<Long>) {
finishActionMode()
val duplicates = taskDuplicator.duplicate(tasks)
onTaskCreated(duplicates)
makeSnackbar(R.string.copy_multiple_tasks_confirmation, duplicates.size.toString()).show()
}
fun clearCollapsed() = taskAdapter.clearCollapsed()
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 EXTRA_COLLAPSED = "extra_collapsed"
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
}
}
}