~williamvds/taskwarrior-android

68b8e628283f5e4627508fea38754a1b699fb194 — williamvds 4 years ago 9740a6f
Remove usage of FormController from EditorActivity

And remove Editor.
This form now only uses AppForm in the backend.
M app/src/main/java/kvj/taskw/data/Task.kt => app/src/main/java/kvj/taskw/data/Task.kt +18 -18
@@ 15,24 15,24 @@ import kotlinx.android.parcel.Parcelize

@Parcelize
data class Task(
    @JvmField val account: UUID,
    @JvmField val uuid: UUID,
    @JvmField val id: Int,
    @JvmField val description: String,
    @JvmField val entry: Date,
    @JvmField val status: Status,
    @JvmField val annotations: List<Annotation> = ArrayList(),
    @JvmField val depends: List<UUID> = ArrayList(),
    @JvmField val tags: List<String> = ArrayList(),
    @JvmField val urgency: Double? = null,
    @JvmField val priority: String? = null,
    @JvmField val project: String? = null,
    @JvmField val due: Date? = null,
    @JvmField val recur: String? = null,
    @JvmField val until: Date? = null,
    @JvmField val wait: Date? = null,
    @JvmField val scheduled: Date? = null,
    @JvmField val start: Date? = null
    @JvmField var account: UUID? = null,
    @JvmField var uuid: UUID? = null,
    @JvmField var id: Int? = null,
    @JvmField var description: String? = null,
    @JvmField var entry: Date? = null,
    @JvmField var status: Status? = null,
    @JvmField var annotations: List<Annotation> = ArrayList(),
    @JvmField var depends: List<UUID> = ArrayList(),
    @JvmField var tags: List<String> = ArrayList(),
    @JvmField var urgency: Double? = null,
    @JvmField var priority: String? = null,
    @JvmField var project: String? = null,
    @JvmField var due: Date? = null,
    @JvmField var recur: String? = null,
    @JvmField var until: Date? = null,
    @JvmField var wait: Date? = null,
    @JvmField var scheduled: Date? = null,
    @JvmField var start: Date? = null
) : Parcelable {
    companion object {
        enum class Status {

D app/src/main/java/kvj/taskw/ui/Editor.java => app/src/main/java/kvj/taskw/ui/Editor.java +0 -151
@@ 1,151 0,0 @@
package kvj.taskw.ui;

import java.util.Calendar;
import java.util.Date;
import java.util.List;

import android.app.DatePickerDialog;
import android.app.TimePickerDialog;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TimePicker;

import org.jetbrains.annotations.NotNull;

import org.kvj.bravo7.form.FormController;
import org.kvj.bravo7.form.impl.widget.ImageViewIntegerAdapter;
import org.kvj.bravo7.form.impl.widget.SpinnerIntegerAdapter;
import org.kvj.bravo7.form.impl.widget.TextViewCharSequenceAdapter;

import kvj.taskw.App;
import kvj.taskw.R;

/**
 * Created by kvorobyev on 11/21/15.
 */
public class Editor extends Fragment {
    private Spinner prioritiesSpinner = null;

    @Override
    public View onCreateView(@NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_editor, container, false);
        setupDatePicker(view, R.id.editor_due, R.id.editor_due_btn);
        setupDatePicker(view, R.id.editor_wait, R.id.editor_wait_btn);
        setupDatePicker(view, R.id.editor_scheduled, R.id.editor_scheduled_btn);
        setupDatePicker(view, R.id.editor_until, R.id.editor_until_btn);
        prioritiesSpinner = view.findViewById(R.id.editor_priority);
        return view;
    }

    public void initForm(FormController form) {
        form.add(new TextViewCharSequenceAdapter(R.id.editor_description, ""), App.KEY_EDIT_DESCRIPTION);
        form.add(new TextViewCharSequenceAdapter(R.id.editor_project, ""), App.KEY_EDIT_PROJECT);
        form.add(new TextViewCharSequenceAdapter(R.id.editor_tags, ""), App.KEY_EDIT_TAGS);
        form.add(new TextViewCharSequenceAdapter(R.id.editor_due, ""), App.KEY_EDIT_DUE);
        form.add(new TextViewCharSequenceAdapter(R.id.editor_wait, ""), App.KEY_EDIT_WAIT);
        form.add(new TextViewCharSequenceAdapter(R.id.editor_scheduled, ""), App.KEY_EDIT_SCHEDULED);
        form.add(new TextViewCharSequenceAdapter(R.id.editor_until, ""), App.KEY_EDIT_UNTIL);
        form.add(new TextViewCharSequenceAdapter(R.id.editor_recur, ""), App.KEY_EDIT_RECUR);
        form.add(new SpinnerIntegerAdapter(R.id.editor_priority, -1), App.KEY_EDIT_PRIORITY);
        form.add(new ImageViewIntegerAdapter(R.id.editor_type_switch, R.drawable.ic_status_pending, R.drawable.ic_status_completed, 0), App.KEY_EDIT_STATUS);
    }



    private static Date dateFromInput(String text) {
        if (TextUtils.isEmpty(text)) {
            return null;
        }
        try {
            return MainListAdapter.formattedFormat.parse(text);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static Date timeFromInput(String text) {
        if (TextUtils.isEmpty(text)) {
            return null;
        }
        try {
            return MainListAdapter.formattedISO.parse(text); // With time
        } catch (Exception e) {
        }
        return dateFromInput(text);
    }

    private void setupDatePicker(View view, int text, int btn) {
        final EditText textInput = view.findViewById(text);
        view.findViewById(btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Date dt = dateFromInput(textInput.getText().toString().trim());
                if (null == dt) { // Invalid input
                    dt = new Date();
                }
                final Calendar c = Calendar.getInstance();
                c.setTime(dt);
                DatePickerDialog dialog = new DatePickerDialog(getContext(), new DatePickerDialog.OnDateSetListener() {
                    @Override
                    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
                        c.set(Calendar.DAY_OF_MONTH, 1);
                        c.set(Calendar.YEAR, year);
                        c.set(Calendar.MONTH, monthOfYear);
                        c.set(Calendar.DAY_OF_MONTH, dayOfMonth);
                        textInput.setText(MainListAdapter.formattedFormat.format(c.getTime()));
                    }
                }, c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
                dialog.show();
            }
        });
        view.findViewById(btn).setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                Date dt = timeFromInput(textInput.getText().toString().trim());
                if (null == dt) { // Invalid input
                    dt = new Date();
                }
                final Calendar c = Calendar.getInstance();
                c.setTime(dt);
                TimePickerDialog dialog = new TimePickerDialog(getContext(), new TimePickerDialog.OnTimeSetListener() {
                    @Override
                    public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
                        c.set(Calendar.HOUR_OF_DAY, hourOfDay);
                        c.set(Calendar.MINUTE, minute);
                        c.set(Calendar.SECOND, 0);
                        textInput.setText(MainListAdapter.formattedISO.format(c.getTime()));
                    }
                }, c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), true);
                dialog.show();
                return true;
            }
        });
    }

    public void setupPriorities(List<String> strings) {
        ArrayAdapter<String> adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, strings);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        prioritiesSpinner.setAdapter(adapter);
    }

    public boolean adding(FormController form) {
        return form.getValue(App.KEY_EDIT_UUID) != null;
    }

    public void show(FormController form) {
        if (adding(form)) { // Add new
            form.getView(App.KEY_EDIT_STATUS).setVisibility(View.VISIBLE);
        } else {
            form.getView(App.KEY_EDIT_STATUS).setVisibility(View.GONE);
        }
    }
}

M app/src/main/java/kvj/taskw/ui/EditorActivity.kt => app/src/main/java/kvj/taskw/ui/EditorActivity.kt +177 -132
@@ 1,134 1,144 @@
package kvj.taskw.ui

import java.util.ArrayList
import java.util.UUID
import java.util.Calendar
import java.util.Date

import android.app.Activity
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.content.Intent
import android.os.Bundle
import android.text.TextUtils
import android.text.format.DateFormat
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ArrayAdapter
import android.widget.EditText

import timber.log.Timber

import org.kvj.bravo7.form.BundleAdapter
import org.kvj.bravo7.form.FormController
import org.kvj.bravo7.form.impl.ViewFinder
import org.kvj.bravo7.form.impl.bundle.StringBundleAdapter
import org.kvj.bravo7.form.impl.widget.TransientAdapter
import org.kvj.bravo7.util.DataUtil

import kvj.taskw.data.Task
import kvj.taskw.data.Task.Companion.Status
import kvj.taskw.App
import kvj.taskw.R
import kvj.taskw.data.AccountController
import kvj.taskw.data.Controller
import kvj.taskw.data.UUIDBundleAdapter

import kotlinx.android.synthetic.main.activity_editor.*
import kotlinx.android.parcel.Parcelize
import java.lang.Exception

class EditorActivity : AppForm<EditorActivity.Form>() {
    override val layout = R.layout.activity_editor

class EditorActivity : AppActivity() {
    private val form = FormController(ViewFinder.ActivityViewFinder(this))
    internal var controller = App.controller<Controller>()
    private var priorities = listOf<String>()
    private var progressListener: AccountController.TaskListener? = null
    private var ac: AccountController? = null
    private var editor: Editor? = null
    private lateinit var progressListener: AccountController.TaskListener
    private lateinit var ac: AccountController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_editor)
        setSupportActionBar(toolbar)

        form.apply {
            add<Any, String>(TransientAdapter(StringBundleAdapter(), null), App.KEY_ACCOUNT)
            add<Any, UUID>  (TransientAdapter(UUIDBundleAdapter(),   null), App.KEY_EDIT_UUID)

            add<Any, Bundle?>(TransientAdapter(object : BundleAdapter<Bundle?>() {
                override fun get(bundle: Bundle?, name: String, def: Bundle?) = bundle?.getBundle(name)
                override fun set(bundle: Bundle?, name: String, value: Bundle?) { bundle?.putBundle(name, value) }
            }, null).oneShot(), App.KEY_EDIT_DATA)

            add<Any, ArrayList<String>>(TransientAdapter(object : BundleAdapter<ArrayList<String>>() {
                override fun get(bundle: Bundle, name: String, def: ArrayList<String>?) = bundle.getStringArrayList(name)
                override fun set(bundle: Bundle, name: String, value: ArrayList<String>?) { bundle.putStringArrayList(name, value) }
            }, null).oneShot(), App.KEY_EDIT_DATA_FIELDS)
        }

        editor = supportFragmentManager.findFragmentById(R.id.editor_editor) as Editor
        editor!!.initForm(form)
        form.load(this@EditorActivity, savedInstanceState)
        progressListener = MainActivity.setupProgressListener(this, progress)
        GetPrioritiesTask(this).execute()

        ac = controller.accountController(form)
        if (ac == null) {
            finish()
            controller.messageShort("Invalid arguments")
            return
        }
        setupDatePicker(editor_due_btn, due)
        setupDatePicker(editor_wait_btn, wait)
        setupDatePicker(editor_scheduled_btn, scheduled)
        setupDatePicker(editor_until_btn, until)
    }

        toolbar.subtitle = ac!!.name()
        progressListener = MainActivity.setupProgressListener(this, progress)
        GetPrioritiesTask(this, savedInstanceState).execute()
    override fun onResume() {
        super.onResume()
        ac.listeners().add(progressListener, true)
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        form.save(outState)
    override fun onPause() {
        super.onPause()
        ac.listeners().remove(progressListener)
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_editor, menu)

        val uuid = form.getValue<UUID>(App.KEY_EDIT_UUID)
        menu.findItem(R.id.menu_tb_add_another).isVisible = (uuid != null)
        menu.findItem(R.id.menu_tb_add_another).isVisible = (data.task != null)

        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.menu_tb_save ->         doSave(false)
            R.id.menu_tb_add_another ->  doSave(true)
            R.id.menu_tb_save ->         submit(false)
            R.id.menu_tb_add_another ->  submit(false)
            R.id.menu_tb_add_shortcut -> createShortcut()
        }

        return true
    }

    override fun onResume() {
        super.onResume()
        ac?.listeners()?.add(progressListener, true)
    }
    override fun loadFromForm() {
        ac = controller.accountController(data.account)
        toolbar.subtitle = ac.name()

    override fun onPause() {
        super.onPause()
        ac?.listeners()?.remove(progressListener)
        val task = data.task ?: return

        description.setText(task.description)
        complete.isChecked = task.status == Status.COMPLETED
        project.setText(task.project)
        tags.setText(MainListAdapter.join(",", task.tags))
        recur.setText(task.recur)

        due.setText(data.due ?: MainListAdapter.formatDate(task.due))
        wait.setText(data.wait ?: MainListAdapter.formatDate(task.wait))
        scheduled.setText(data.scheduled ?: MainListAdapter.formatDate(task.scheduled))
        until.setText(data.until ?: MainListAdapter.formatDate(task.until))

        data.original = data.original ?: task.copy()
    }

    override fun onBackPressed() {
        if (!form.changed()) { // No changes - just close
            super.onBackPressed()
            return
    override fun saveToForm() {
        data.task?.let {
            it.description = description.text.toString()
            it.status = if (complete.isChecked) Status.COMPLETED else Status.PENDING
            it.project = project.text.toString()
            it.tags = tags.text.toString().split("([,;]\\s*|\\s+)".toRegex())
            it.priority = priority.selectedItem as String
            it.recur = recur.text.toString()
        }

        data.let {
            it.due = due.text.toString()
            it.wait = wait.text.toString()
            it.scheduled = scheduled.text.toString()
            it.until = until.text.toString()
        }

        Timber.d("Changed: %s", form.changes())
        controller.question(this,
            "There are some changes, discard?",
            Runnable { super@EditorActivity.onBackPressed() },
            null)
        data.let {
            it.due = due.text.toString()
            it.wait = wait.text.toString()
            it.scheduled = scheduled.text.toString()
            it.until = until.text.toString()
        }
    }

    private fun createShortcut() {
        val ac = ac
        ac ?: return
    override fun hasChanges(): Boolean {
        return !data.due.isNullOrEmpty()
                || !data.wait.isNullOrEmpty()
                || !data.scheduled.isNullOrEmpty()
                || !data.until.isNullOrEmpty()
                || (data.task?.equals(data.original) ?: true)
    }

    private fun submit(addAnother: Boolean) {
        super.submit()
        SaveTask(this, addAnother).execute()
    }

        val bundle = Bundle()
        form.save(bundle)
        bundle.remove(App.KEY_EDIT_UUID) // Just in case
    private fun createShortcut() {
        saveToForm()

        val shortcutIntent = Intent(this, EditorActivity::class.java).apply {
            putExtras(bundle)
            putExtra(App.KEY_EDIT_DATA, data)
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }



@@ 140,74 150,108 @@ class EditorActivity : AppActivity() {
        controller.input(this, "Shortcut name:", ac.name(), callback, null)
    }

    private fun propertyChange(key: String, modifier: String): String {
        var value = form.getValue<String>(key)
        if (value.isBlank()) value = ""
    private fun setupDatePicker(button: View, input: EditText) {
        fun getCalendar(input: EditText): Calendar {
            val date = try {
                MainListAdapter.formattedFormat.parse(input.text.toString().trim()) ?: Date()
            } catch (ex: Exception) {
                Date()
            }

        return String.format("%s:%s", modifier, value)
    }
            return Calendar.getInstance().apply { time = date }
        }

        button.setOnClickListener {
            val calendar = getCalendar(input)

    private fun save(): String? {
        if (!form.changed()) return "Nothing has been changed"

        val description = form.getValue<String>(App.KEY_EDIT_DESCRIPTION)
        if (TextUtils.isEmpty(description)) return "Description is mandatory"

        val uuid      = form.getValue<UUID>(App.KEY_EDIT_UUID)
        val completed = form.getValue(App.KEY_EDIT_STATUS,   Int::class.java) > 0
        val priority  = form.getValue(App.KEY_EDIT_PRIORITY, Int::class.java)

        val changes = ArrayList<String>()
        form.changes().forEach { key ->
            when (key) {
                App.KEY_EDIT_DESCRIPTION ->  changes.add(AccountController.escape(description))
                App.KEY_EDIT_PROJECT ->      changes.add(propertyChange(key, "project"))
                App.KEY_EDIT_DUE ->          changes.add(propertyChange(key, "due"))
                App.KEY_EDIT_SCHEDULED ->    changes.add(propertyChange(key, "scheduled"))
                App.KEY_EDIT_WAIT ->         changes.add(propertyChange(key, "wait"))
                App.KEY_EDIT_UNTIL ->        changes.add(propertyChange(key, "until"))
                App.KEY_EDIT_RECUR ->        changes.add(propertyChange(key, "recur"))
                App.KEY_EDIT_PRIORITY ->     changes.add("priority:${priorities[priority]}")
                App.KEY_EDIT_TAGS -> {
                    val tagsStr = form.getValue<String>(App.KEY_EDIT_TAGS)
                    val tags = tagsStr.split("([,;]\\s*|\\s+)".toRegex())
                    changes.add(String.format("tags:%s", tags.joinToString(",")))
            DatePickerDialog(this, { _, year, month, day ->
                calendar.set(year, month, day)
                input.setText(MainListAdapter.formattedFormat.format(calendar.time))
            }, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)).show()
        }

        button.setOnLongClickListener {
            val calendar = getCalendar(input)
            val format24 = DateFormat.is24HourFormat(this)

            TimePickerDialog(this, TimePickerDialog.OnTimeSetListener { _, hour, minute ->
                calendar.apply {
                    set(Calendar.HOUR_OF_DAY, hour)
                    set(Calendar.MINUTE, minute)
                    set(Calendar.SECOND, 0)
                }

                input.setText(MainListAdapter.formattedISO.format(calendar.time))
            }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), format24).show()

            true
        }
    }

    fun save(): String? {
        val changes = mutableListOf<String>()

        val map = mapOf(
            "description" to Pair(data.task?.description, data.original?.description),
            "project" to Pair(data.task?.project, data.original?.project),
            "priority" to Pair(data.task?.priority, data.original?.priority),
            "due" to Pair(data.due, data.original?.due),
            "scheduled" to Pair(data.scheduled, data.original?.scheduled),
            "wait" to Pair(data.wait, data.original?.wait),
            "until" to Pair(data.until, data.original?.until),
            "recur" to Pair(data.task?.recur, data.original?.recur),
            "tags" to Pair(data.task?.tags?.joinToString(separator = ","), data.original?.tags?.joinToString(separator = ","))
        )

        for ((key, pair) in map) {
            if (pair.first == pair.second) continue

            val value = when (pair.first) {
                null -> ""
                else -> AccountController.escape(pair.first.toString())
            }

            changes.add("$key:$value")
        }

        Timber.d("Saving change: %s %s %s", uuid, changes, completed)
        val completed = data.task?.status == Status.COMPLETED
        Timber.d("Saving change: UUID %s, changes %s %s", data.task?.uuid, changes, completed)

        uuid?.let { return ac!!.taskModify(uuid, changes) }
        return if (completed) ac!!.taskLog(changes) else ac!!.taskAdd(changes)
        data.task?.uuid?.let { return ac.taskModify(it, changes) }
        return if (completed) ac.taskLog(changes) else ac.taskAdd(changes)
    }

    private fun doSave(addAnother: Boolean) {
        SaveTask(this, addAnother).execute()
    }
    @Parcelize
    data class Form @JvmOverloads constructor(
            val account: String,
            var task: Task? = null,
            var original: Task? = null,
            var due: String? = null,
            var wait: String? = null,
            var scheduled: String? = null,
            var until: String? = null
    ) : FormData

    companion object {
        private class GetPrioritiesTask(activity: EditorActivity,
                                        val bundle: Bundle?)
            : StaticAsyncTask<EditorActivity, Void, Void, List<String>>(activity) {

            override fun EditorActivity.background(vararg params: Void): List<String> {
                return ac!!.taskPriority()
        @JvmStatic
        fun start(activity: Activity, data: Form) {
            val intent = Intent(activity, EditorActivity::class.java).apply {
                putExtra(App.KEY_EDIT_DATA, data)
            }

            override fun EditorActivity.finish(result: List<String>) {
                priorities = result
                editor!!.setupPriorities(priorities)
                form.load(this, bundle, App.KEY_EDIT_PRIORITY)
                editor!!.show(form)
            activity.startActivityForResult(intent, App.EDIT_REQUEST)
        }

                val formData = form.getValue<Bundle>(App.KEY_EDIT_DATA)
                val fields   = form.getValue<List<String>>(App.KEY_EDIT_DATA_FIELDS)
        private class GetPrioritiesTask(activity: EditorActivity)
            : StaticAsyncTask<EditorActivity, Void, Void, List<String>>(activity) {

                Timber.d("Edit: %s %s", formData, fields)
            override fun EditorActivity.background(vararg params: Void): List<String>
                = ac.taskPriority()

                if (formData == null || fields == null) return
                fields.forEach { form.setValue(it, formData.getString(it)) }
            override fun EditorActivity.finish(result: List<String>) {
                priorities = result
                priority.adapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, priorities)
                priority.setSelection(priorities.indexOf(data.task?.priority ?: ""))
            }
        }



@@ 215,7 259,9 @@ class EditorActivity : AppActivity() {
                               val addAnother: Boolean)
            : StaticAsyncTask<EditorActivity, Void, Void, String?>(context) {

            override fun EditorActivity.background(vararg params: Void) = save()
            override fun EditorActivity.background(vararg params: Void): String? {
                return save()
            }

            override fun EditorActivity.finish(result: String?) {
                if (!result.isNullOrBlank()) {


@@ 223,9 269,8 @@ class EditorActivity : AppActivity() {
                    return
                }

                val uuid = form.getValue<UUID>(App.KEY_EDIT_UUID)
                controller.messageShort(
                    getString(if (uuid != null) R.string.edit_task_success else R.string.add_task_success))
                    getString(if (data.task != null) R.string.edit_task_success else R.string.add_task_success))

                setResult(Activity.RESULT_OK)



@@ 234,8 279,8 @@ class EditorActivity : AppActivity() {
                    return
                }

                form.setValue(App.KEY_EDIT_DESCRIPTION, "")
                form.getView<View>(App.KEY_EDIT_DESCRIPTION).requestFocus()
                data.task = null
                description.requestFocus()
            }
        }
    }

M app/src/main/java/kvj/taskw/ui/MainActivity.java => app/src/main/java/kvj/taskw/ui/MainActivity.java +2 -21
@@ 394,31 394,12 @@ public class MainActivity extends AppActivity implements Controller.ToastMessage

    private void add(Pair<String, String>... pairs) {
        if (null == ac) return;
        Intent intent = new Intent(this, EditorActivity.class);
        ac.intentForEditor(intent, null);
        if (null != pairs) {
            Bundle data = new Bundle();
            ArrayList<String> names = new ArrayList<>();
            for (Pair<String, String> pair : pairs) { // $COMMENT
                if (!TextUtils.isEmpty(pair.second)) { // Has data
                    data.putString(pair.first, pair.second);
                    names.add(pair.first);
                }
            }
            intent.putExtra(App.KEY_EDIT_DATA, data);
            intent.putStringArrayListExtra(App.KEY_EDIT_DATA_FIELDS, names);
        }
        startActivityForResult(intent, App.EDIT_REQUEST);
        EditorActivity.start(this, new EditorActivity.Form(ac.id()));
    }

    private void edit(@NotNull Task task) {
        if (null == ac) return;
        Intent intent = new Intent(this, EditorActivity.class);
        if (ac.intentForEditor(intent, task.uuid)) { // Valid task
            startActivityForResult(intent, App.EDIT_REQUEST);
        } else {
            controller.messageShort("Invalid task");
        }
        EditorActivity.start(this, new EditorActivity.Form(task.account.toString(), task));
    }

    @Override

M app/src/main/java/kvj/taskw/ui/TaskActivity.kt => app/src/main/java/kvj/taskw/ui/TaskActivity.kt +7 -11
@@ 77,16 77,12 @@ class TaskActivity : AppActivity() {
        add_tag.setOnClickListener {
            TagDialog.start(this, TagDialog.Form(
                task.account.toString(),
                task.uuid
                task.uuid!!
            ))
        }

        fun openEditor() {
            EditTask(this) {
                val intent = Intent(this, EditorActivity::class.java)
                it.intentForEditor(intent, task.uuid)
                startActivityForResult(intent, App.EDIT_REQUEST)
            }.execute()
            EditorActivity.start(this, EditorActivity.Form(task.account.toString(), task))
        }

        edit.setOnClickListener {


@@ 102,7 98,7 @@ class TaskActivity : AppActivity() {

        done.setOnClickListener {
            EditTask(this@TaskActivity) {
                it.taskDone(task.uuid)
                it.taskDone(task.uuid!!)
                finish()
            }.execute()



@@ 120,7 116,7 @@ class TaskActivity : AppActivity() {
        annotate.setOnClickListener {
            AnnotationDialog.start(this, AnnotationDialog.Form(
                task.account.toString(),
                task.uuid
                task.uuid!!
            ))

            hideEditList()


@@ 203,7 199,7 @@ class TaskActivity : AppActivity() {
            setMessage(getString(R.string.remove_tag_dialog_format, tag))
            setPositiveButton(android.R.string.yes) { _, _ ->
                EditTask(this@TaskActivity) {
                    it.taskRemoveTag(task.uuid, tag)
                    it.taskRemoveTag(task.uuid!!, tag)
                }.execute()
            }
            setNegativeButton(android.R.string.no, null)


@@ 227,7 223,7 @@ class TaskActivity : AppActivity() {
                task_ann_date.text = MainListAdapter.formatDate(annotation.entry)
                task_ann_delete_btn.setOnClickListener {
                    EditTask(activity) {
                        it.taskDenotate(activity.task.uuid, annotation.description)
                        it.taskDenotate(activity.task.uuid!!, annotation.description)
                    }.execute()
                }
            }


@@ 267,7 263,7 @@ class TaskActivity : AppActivity() {
            override fun TaskActivity.background(vararg params: Void): Task {
                val account = task.account
                val controller = App.controller<Controller>().accountController(account.toString())
                return controller.getTask(task.uuid)
                return controller.getTask(task.uuid!!)
            }

            override fun TaskActivity.finish(result: Task) {

M app/src/main/res/layout/activity_editor.xml => app/src/main/res/layout/activity_editor.xml +214 -18
@@ 1,33 1,229 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="0"/>
        android:layout_weight="0" />

    <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">
            <fragment
                    android:id="@+id/editor_editor"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="5dp"
                    android:orientation="horizontal">

                    <CheckBox
                        android:id="@+id/complete"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="8dp"
                        android:layout_marginRight="8dp"
                        android:layout_gravity="center_vertical"
                        android:layout_weight="0" />

                    <android.support.design.widget.TextInputLayout
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_gravity="bottom"
                        android:layout_weight="1">

                        <EditText
                            android:id="@+id/description"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="@string/task_description"
                            android:importantForAutofill="no"
                            android:inputType="text"
                            android:singleLine="true" />
                    </android.support.design.widget.TextInputLayout>

                    <Spinner
                        android:id="@+id/priority"
                        style="@style/Base.Widget.AppCompat.Spinner.Underlined"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="bottom"
                        android:layout_weight="0"
                        android:minWidth="50dp" />
                </LinearLayout>

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:baselineAligned="false"
                    android:orientation="horizontal">

                    <android.support.design.widget.TextInputLayout
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1">

                        <EditText
                            android:id="@+id/project"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="@string/task_project"
                            android:importantForAutofill="no"
                            android:inputType="text"
                            android:singleLine="true" />
                    </android.support.design.widget.TextInputLayout>

                    <android.support.design.widget.TextInputLayout
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1">

                        <EditText
                            android:id="@+id/tags"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="@string/task_tags"
                            android:importantForAutofill="no"
                            android:inputType="text"
                            android:singleLine="true" />
                    </android.support.design.widget.TextInputLayout>
                </LinearLayout>

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">

                    <android.support.design.widget.TextInputLayout
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1">

                        <EditText
                            android:id="@+id/due"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="@string/task_due"
                            android:importantForAutofill="no"
                            android:inputType="text"
                            android:singleLine="true" />
                    </android.support.design.widget.TextInputLayout>

                    <android.widget.ImageButton
                        android:id="@+id/editor_due_btn"
                        style="@style/IconButton"
                        android:src="@drawable/ic_action_date" />

                    <android.support.design.widget.TextInputLayout
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1">

                        <EditText
                            android:id="@+id/wait"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="@string/task_wait"
                            android:importantForAutofill="no"
                            android:inputType="text"
                            android:singleLine="true" />
                    </android.support.design.widget.TextInputLayout>

                    <android.widget.ImageButton
                        android:id="@+id/editor_wait_btn"
                        style="@style/IconButton"
                        android:src="@drawable/ic_action_date" />
                </LinearLayout>

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">

                    <android.support.design.widget.TextInputLayout
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1">

                        <EditText
                            android:id="@+id/scheduled"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="@string/task_scheduled"
                            android:importantForAutofill="no"
                            android:inputType="text"
                            android:singleLine="true" />
                    </android.support.design.widget.TextInputLayout>

                    <android.widget.ImageButton
                        android:id="@+id/editor_scheduled_btn"
                        style="@style/IconButton"
                        android:src="@drawable/ic_action_date" />
                </LinearLayout>

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    class="kvj.taskw.ui.Editor"/>
                    android:orientation="horizontal">

                    <android.support.design.widget.TextInputLayout
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1">

                        <EditText
                            android:id="@+id/recur"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="@string/task_recur"
                            android:importantForAutofill="no"
                            android:inputType="text"
                            android:singleLine="true" />
                    </android.support.design.widget.TextInputLayout>

                    <android.support.design.widget.TextInputLayout
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1">

                        <EditText
                            android:id="@+id/until"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="@string/task_until"
                            android:importantForAutofill="no"
                            android:inputType="text"
                            android:singleLine="true" />
                    </android.support.design.widget.TextInputLayout>

                    <android.widget.ImageButton
                        android:id="@+id/editor_until_btn"
                        style="@style/IconButton"
                        android:src="@drawable/ic_action_date" />
                </LinearLayout>
            </LinearLayout>
        </ScrollView>

        <ProgressBar
                style="@style/Widget.AppCompat.ProgressBar.Horizontal"
                android:visibility="gone"
                android:id="@+id/progress"
                android:layout_alignParentTop="true"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:indeterminate="true"/>
            android:id="@+id/progress"
            style="@style/Widget.AppCompat.ProgressBar.Horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:indeterminate="true"
            android:visibility="gone" />
    </RelativeLayout>

</LinearLayout>