~williamvds/taskwarrior-android

9740a6ff3a781f7acd5e84222cb7f4012bf9c33f — williamvds 5 years ago 085a812
Convert RunActivity to Kotlin and an AppForm

The activity no longer uses a RecyclerView for the output. This has been
replaced by a multi-line TextView

Added command_result_format string, which provides to format in which to
display the command output

AccountController#StringAggregator has been made public static so that
it can be used within RunActivity
M app/src/main/AndroidManifest.xml => app/src/main/AndroidManifest.xml +1 -2
@@ 62,8 62,7 @@
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <activity android:name=".ui.RunActivity">
        </activity>
        <activity android:name=".ui.RunActivity" />

        <activity android:name=".ui.TagDialog" />


M app/src/main/java/kvj/taskw/data/AccountController.java => app/src/main/java/kvj/taskw/data/AccountController.java +2 -9
@@ 45,7 45,6 @@ import timber.log.Timber;
import kvj.taskw.App;
import kvj.taskw.sync.SSLHelper;
import kvj.taskw.ui.MainListAdapter;
import kvj.taskw.ui.RunActivity;
import kvj.taskw.notifications.NotificationFactory;
import kvj.taskw.notifications.NotificationChannels;
import kvj.taskw.ui.StaticAsyncTask;


@@ 255,7 254,7 @@ public class AccountController {
        new LoadNotificationsTask(this).execute();
    }

    private class StringAggregator implements StreamConsumer {
    public static class StringAggregator implements StreamConsumer {

        StringBuilder builder = new StringBuilder();



@@ 267,7 266,7 @@ public class AccountController {
            builder.append(line);
        }

        private String text() {
        public String text() {
            return builder.toString();
        }
    }


@@ 900,12 899,6 @@ public class AccountController {
        return query.replace(" ", "\\ "); //.replace("(", "\\(").replace(")", "\\)");
    }

    public Intent intentForRunTask() {
        Intent intent = new Intent(controller.context(), RunActivity.class);
        intent.putExtra(App.KEY_ACCOUNT, id);
        return intent;
    }

    public boolean intentForEditor(Intent intent, UUID uuid) {
        intent.putExtra(App.KEY_ACCOUNT, id);
        List<String> priorities = taskPriority();

M app/src/main/java/kvj/taskw/ui/MainActivity.java => app/src/main/java/kvj/taskw/ui/MainActivity.java +1 -1
@@ 214,7 214,7 @@ public class MainActivity extends AppActivity implements Controller.ToastMessage
                refreshAccount(account);
                break;
            case R.id.menu_nav_run:
                startActivity(ac.intentForRunTask());
                RunActivity.start(this, new RunActivity.Form(account));
                break;
            case R.id.menu_nav_debug:
                Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);

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

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.ShareActionProvider;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;

import timber.log.Timber;

import org.jetbrains.annotations.NotNull;
import org.kvj.bravo7.form.FormController;
import org.kvj.bravo7.form.impl.ViewFinder;
import org.kvj.bravo7.form.impl.bundle.ListStringBundleAdapter;
import org.kvj.bravo7.form.impl.bundle.StringBundleAdapter;
import org.kvj.bravo7.form.impl.widget.TextViewCharSequenceAdapter;
import org.kvj.bravo7.form.impl.widget.TransientAdapter;

import java.util.ArrayList;
import java.util.List;

import kvj.taskw.App;
import kvj.taskw.R;
import kvj.taskw.data.AccountController;
import kvj.taskw.data.Controller;

/**
 * Created by vorobyev on 12/1/15.
 */
public class RunActivity extends AppActivity {

    FormController form = new FormController(new ViewFinder.ActivityViewFinder(this));
    Controller controller = App.controller();
    private AccountController ac = null;
    private RunAdapter adapter = null;
    private kvj.taskw.data.AccountController.TaskListener progressListener = null;
    private ShareActionProvider mShareActionProvider = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_run);
        Toolbar toolbar = findViewById(R.id.toolbar);
        RecyclerView list = findViewById(R.id.run_output);
        list.setLayoutManager(new LinearLayoutManager(this));
        setSupportActionBar(toolbar);
        final EditText input = findViewById(R.id.run_command);
        input.setOnKeyListener(new View.OnKeyListener() {
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (keyCode != KeyEvent.KEYCODE_ENTER) return false;

                if (event.getAction() == KeyEvent.ACTION_DOWN) {
                    run();
                }

                return true;
            }
        });
        form.add(new TransientAdapter<>(new StringBundleAdapter(), null), App.KEY_ACCOUNT);
        form.add(new TransientAdapter<>(new ListStringBundleAdapter(), null), App.KEY_RUN_OUTPUT);
        form.add(new TextViewCharSequenceAdapter(R.id.run_command, null), App.KEY_RUN_COMMAND);
        form.load(this, savedInstanceState);
        progressListener = MainActivity
            .setupProgressListener(this, (ProgressBar) findViewById(R.id.progress));
        ac = controller.accountController(form);
        if (null == ac) {
            controller.messageShort("Invalid arguments");
            finish();
            return;
        }
        adapter = new RunAdapter(form.getValue(App.KEY_RUN_OUTPUT, ArrayList.class));
        list.setAdapter(adapter);
        toolbar.setSubtitle(ac.name());
    }

    private void shareAll() {
        CharSequence text = adapter.allText();
        if (TextUtils.isEmpty(text)) { // Error
            controller.messageShort("Nothing to share");
            return;
        }
        Intent sendIntent = new Intent();
        sendIntent.setAction(Intent.ACTION_SEND);
        sendIntent.putExtra(Intent.EXTRA_TEXT, text);
        sendIntent.setType("text/plain");
        if (null != mShareActionProvider) {
            Timber.d("Share provider set");
            mShareActionProvider.setShareIntent(sendIntent);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_run, menu);

        // Locate MenuItem with ShareActionProvider
        MenuItem item = menu.findItem(R.id.menu_tb_run_share);

        // Fetch and store ShareActionProvider
        mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item);

        // Return true to display menu
        return true;

    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_tb_run_run:
                run();
                return true;
            case R.id.menu_tb_run_copy:
                copyAll();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void copyAll() {
        CharSequence text = adapter.allText();
        if (TextUtils.isEmpty(text)) { // Error
            controller.messageShort("Nothing to copy");
            return;
        }
        controller.copyToClipboard(text);
    }

    private static class RunTask extends StaticAsyncTask<RunActivity, Void, Void, Boolean> {
        private String input;
        private AccountController.ListAggregator out, err;

        RunTask(RunActivity activity, String input, AccountController.ListAggregator out, AccountController.ListAggregator err) {
            super(activity);
            this.input = input;
            this.out = out;
            this.err = err;
        }

        @Override
        protected Boolean background(RunActivity activity, Void... params) {
            int result = activity.ac.taskCustom(input, out, err);
            return 0 == result;
        }

        @Override
        protected void finish(RunActivity activity, Boolean result) {
            out.data().addAll(err.data());
            activity.adapter.addAll(out.data());
            activity.shareAll();
        }
    }

    private void run() {
        final String input = form.getValue(App.KEY_RUN_COMMAND);
        if (TextUtils.isEmpty(input)) {
            controller.messageShort("Input is empty");
            return;
        }
        adapter.clear();
        final AccountController.ListAggregator out = new AccountController.ListAggregator();
        final AccountController.ListAggregator err = new AccountController.ListAggregator();
        new RunTask(this, input, out, err).execute();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (null != adapter) {
            form.setValue(App.KEY_RUN_OUTPUT, adapter.data);
        }
        form.save(outState);
    }

    @Override
    protected void onResume() {
        super.onResume();
        ac.listeners().add(progressListener, true);
    }

    @Override
    protected void onPause() {
        super.onPause();
        ac.listeners().remove(progressListener);
    }

    class RunAdapter extends RecyclerView.Adapter<RunAdapter.RunAdapterItem> {

        ArrayList<String> data = new ArrayList<>();

        public RunAdapter(ArrayList<String> data) {
            if (null != data) {
                this.data.addAll(data);
            }
        }

        @NotNull
        @Override
        public RunAdapterItem onCreateViewHolder(@NotNull ViewGroup parent, int viewType) {
            return new RunAdapterItem(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_run_output, parent, false));
        }

        @Override
        public void onBindViewHolder(@NotNull RunAdapterItem holder, int position) {
            holder.text.setText(data.get(position));
        }

        @Override
        public int getItemCount() {
            return data.size();
        }

        void clear() {
            notifyItemRangeRemoved(0, data.size());
            data.clear();
        }

        public synchronized void addAll(List<String> data) {
            int from = getItemCount();
            this.data.addAll(data);
            notifyItemRangeInserted(from, data.size());
        }

        public CharSequence allText() {
            StringBuilder sb = new StringBuilder();
            for (String line : data) { // Copy to
                if (sb.length() > 0) {
                    sb.append('\n');
                }
                sb.append(line);
            }
            return sb;
        }

        class RunAdapterItem extends RecyclerView.ViewHolder implements View.OnLongClickListener {

            private final TextView text;

            public RunAdapterItem(View itemView) {
                super(itemView);
                itemView.setOnLongClickListener(this);
                this.text = itemView.findViewById(R.id.run_item_text);
            }

            @Override
            public boolean onLongClick(View v) {
                controller.copyToClipboard(data.get(getAdapterPosition()));
                return true;
            }
        }
    }

}

A app/src/main/java/kvj/taskw/ui/RunActivity.kt => app/src/main/java/kvj/taskw/ui/RunActivity.kt +148 -0
@@ 0,0 1,148 @@
package kvj.taskw.ui

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.support.v4.view.MenuItemCompat
import android.support.v7.widget.ShareActionProvider
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.view.View

import timber.log.Timber

import kvj.taskw.App
import kvj.taskw.R
import kvj.taskw.data.AccountController
import kvj.taskw.data.AccountController.TaskListener

import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.activity_run.*

class RunActivity : AppForm<RunActivity.Form>() {
    override val layout = R.layout.activity_run

    private lateinit var ac: AccountController
    private lateinit var progressListener: TaskListener
    private lateinit var shareProvider: ShareActionProvider

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setSupportActionBar(toolbar)

        input.setOnKeyListener(View.OnKeyListener { _, keyCode, event ->
            if (keyCode != KeyEvent.KEYCODE_ENTER) return@OnKeyListener false
            if (event.action == KeyEvent.ACTION_DOWN) submit()
            true
        })

        progressListener = MainActivity.setupProgressListener(this, progress)
    }

    override fun onResume() {
        super.onResume()
        ac.listeners().add(progressListener, true)
    }

    override fun onPause() {
        super.onPause()
        ac.listeners().remove(progressListener)
    }

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

        val item = menu.findItem(R.id.menu_tb_run_share)
        shareProvider = MenuItemCompat.getActionProvider(item) as ShareActionProvider

        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.menu_tb_run_run -> {
                submit()
                return true
            }
            R.id.menu_tb_run_copy -> {
                copyAll()
                return true
            }
        }

        return super.onOptionsItemSelected(item)
    }

    override fun submit() {
        super.submit()
        RunTask(this).execute()
    }

    override fun loadFromForm() {
        input.setText(data.input)
        output.text = data.output

        ac = controller.accountController(data.account)
        toolbar.subtitle = ac.name()
        setShareIntent()
    }

    override fun saveToForm() {
        data.input = input.text.toString()
        data.output = output.text.toString()
    }

    override fun hasChanges() = false

    private fun setShareIntent() {
        if (!::shareProvider.isInitialized) {
            Timber.e("Share provider not initialized!")
            return
        }

        shareProvider.setShareIntent(Intent().apply {
            action = Intent.ACTION_SEND
            putExtra(Intent.EXTRA_TEXT, output.text)
            type = "text/plain"
        })
    }

    private fun copyAll() {
        controller.copyToClipboard(output.text)
    }

    @Parcelize
    data class Form @JvmOverloads constructor(
            val account: String,
            var input: String? = null,
            var output: String? = null
    ) : FormData

    companion object {
        @JvmStatic
        fun start(activity: Activity, data: Form) {
            val intent = Intent(activity, RunActivity::class.java).apply {
                putExtra(App.KEY_EDIT_DATA, data)
            }

            activity.startActivity(intent)
        }

        private class RunTask(activity: RunActivity)
            : StaticAsyncTask<RunActivity, Void, Void, Boolean>(activity) {
            private val out = AccountController.StringAggregator()
            private val err = AccountController.StringAggregator()

            override fun RunActivity.background(vararg params: Void): Boolean {
                val result = ac.taskCustom(data.input, out, err)
                return result == 0
            }

            override fun RunActivity.finish(result: Boolean) {
                output.text = getString(R.string.command_result_format, out.text(), err.text())
                setShareIntent()
            }
        }
    }
}

M app/src/main/res/layout/activity_run.xml => app/src/main/res/layout/activity_run.xml +8 -4
@@ 34,9 34,10 @@
                    android:layout_marginTop="6dp">

                    <EditText
                        android:id="@+id/run_command"
                        android:id="@+id/input"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginBottom="8dp"
                        android:hint="@string/command_prompt_hint"
                        android:imeOptions="actionSend"
                        android:importantForAutofill="no"


@@ 48,12 49,15 @@
                </android.support.design.widget.TextInputLayout>
            </LinearLayout>

            <android.support.v7.widget.RecyclerView
                android:id="@+id/run_output"
            <TextView
                android:id="@+id/output"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:scrollbars="vertical" />
                android:fontFamily="monospace"
                android:singleLine="false"
                android:textColor="?android:attr/textColorPrimary"
                android:textIsSelectable="true" />
        </LinearLayout>

        <ProgressBar

M app/src/main/res/values/strings.xml => app/src/main/res/values/strings.xml +1 -0
@@ 100,6 100,7 @@
    <!-- Custom command dialog -->
    <string name="command_prompt_hint">Arguments</string>
    <string name="run">Run</string>
    <string name="command_result_format">%1$s\n\nErrors:\n%2$s</string>

    <!-- New account dialog -->
    <string name="account_name_hint">Name</string>