diff --git a/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java b/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java index b76203410..e32002bc5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java @@ -18,28 +18,34 @@ package com.keylesspalace.tusky; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; import android.support.v7.app.ActionBar; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.Button; import android.widget.EditText; -import android.widget.TextView; import android.widget.Toast; import com.android.volley.AuthFailureError; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyError; +import com.android.volley.toolbox.JsonArrayRequest; import com.android.volley.toolbox.JsonObjectRequest; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class ReportActivity extends BaseActivity { @@ -48,6 +54,7 @@ public class ReportActivity extends BaseActivity { private String domain; private String accessToken; private View anyView; // what Snackbar will use to find the root view + private ReportAdapter adapter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -57,7 +64,7 @@ public class ReportActivity extends BaseActivity { Intent intent = getIntent(); final String accountId = intent.getStringExtra("account_id"); String accountUsername = intent.getStringExtra("account_username"); - final String statusId = intent.getStringExtra("status_id"); + String statusId = intent.getStringExtra("status_id"); String statusContent = intent.getStringExtra("status_content"); SharedPreferences preferences = getSharedPreferences( @@ -75,18 +82,39 @@ public class ReportActivity extends BaseActivity { } anyView = toolbar; - TextView content = (TextView) findViewById(R.id.report_status_content); - content.setText(HtmlUtils.fromHtml(statusContent)); + final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.report_recycler_view); + recyclerView.setHasFixedSize(true); + LinearLayoutManager layoutManager = new LinearLayoutManager(this); + recyclerView.setLayoutManager(layoutManager); + adapter = new ReportAdapter(); + recyclerView.setAdapter(adapter); + + DividerItemDecoration divider = new DividerItemDecoration( + this, layoutManager.getOrientation()); + Drawable drawable = ThemeUtils.getDrawable(this, R.attr.report_status_divider_drawable, + R.drawable.report_status_divider_dark); + divider.setDrawable(drawable); + recyclerView.addItemDecoration(divider); + + ReportAdapter.ReportStatus reportStatus = new ReportAdapter.ReportStatus(statusId, + HtmlUtils.fromHtml(statusContent), true); + adapter.addItem(reportStatus); final EditText comment = (EditText) findViewById(R.id.report_comment); Button send = (Button) findViewById(R.id.report_send); send.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - String[] statusIds = new String[] { statusId }; - sendReport(accountId, statusIds, comment.getText().toString()); + String[] statusIds = adapter.getCheckedStatusIds(); + if (statusIds.length > 0) { + sendReport(accountId, statusIds, comment.getText().toString()); + } else { + comment.setError(getString(R.string.error_report_too_few_statuses)); + } } }); + + fetchRecentStatuses(accountId); } /* JSONArray has a constructor to take primitive arrays but it's restricted to API level 19 and @@ -107,8 +135,7 @@ public class ReportActivity extends BaseActivity { parameters.put("status_ids", makeStringArrayCompat(statusIds)); parameters.put("comment", comment); } catch (JSONException e) { - Log.e(TAG, "Not all the report parameters have been properly set. " - + e.getMessage()); + Log.e(TAG, "Not all the report parameters have been properly set. " + e.getMessage()); onSendFailure(accountId, statusIds, comment); return; } @@ -155,4 +182,51 @@ public class ReportActivity extends BaseActivity { }) .show(); } + + private void fetchRecentStatuses(String accountId) { + String endpoint = String.format(getString(R.string.endpoint_statuses), accountId); + String url = "https://" + domain + endpoint; + JsonArrayRequest request = new JsonArrayRequest(url, + new Response.Listener() { + @Override + public void onResponse(JSONArray response) { + List statusList; + try { + statusList = Status.parse(response); + } catch (JSONException e) { + onFetchStatusesFailure(e); + return; + } + // Add all the statuses except reblogs. + List itemList = new ArrayList<>(); + for (Status status : statusList) { + if (status.getRebloggedByDisplayName() == null) { + ReportAdapter.ReportStatus item = new ReportAdapter.ReportStatus( + status.getId(), status.getContent(), false); + itemList.add(item); + } + } + adapter.addItems(itemList); + } + }, + new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + onFetchStatusesFailure(error); + } + }) { + @Override + public Map getHeaders() throws AuthFailureError { + Map headers = new HashMap<>(); + headers.put("Authorization", "Bearer " + accessToken); + return headers; + } + }; + request.setTag(TAG); + VolleySingleton.getInstance(this).addToRequestQueue(request); + } + + private void onFetchStatusesFailure(Exception exception) { + Log.e(TAG, "Failed to fetch recent statuses to report. " + exception.getMessage()); + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/ReportAdapter.java b/app/src/main/java/com/keylesspalace/tusky/ReportAdapter.java new file mode 100644 index 000000000..672376e53 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/ReportAdapter.java @@ -0,0 +1,121 @@ +package com.keylesspalace.tusky; + +import android.support.v7.widget.RecyclerView; +import android.text.Spanned; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +class ReportAdapter extends RecyclerView.Adapter { + static class ReportStatus { + String id; + Spanned content; + boolean checked; + + ReportStatus(String id, Spanned content, boolean checked) { + this.id = id; + this.content = content; + this.checked = checked; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (this.id == null) { + return this == other; + } else if (!(other instanceof ReportStatus)) { + return false; + } + ReportStatus status = (ReportStatus) other; + return status.id.equals(this.id); + } + } + + private List statusList; + + ReportAdapter() { + super(); + statusList = new ArrayList<>(); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_report_status, parent, false); + return new ReportStatusViewHolder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { + ReportStatusViewHolder holder = (ReportStatusViewHolder) viewHolder; + ReportStatus status = statusList.get(position); + holder.setupWithStatus(status); + } + + @Override + public int getItemCount() { + return statusList.size(); + } + + void addItem(ReportStatus status) { + int end = statusList.size(); + statusList.add(status); + notifyItemInserted(end); + } + + void addItems(List newStatuses) { + int end = statusList.size(); + int added = 0; + for (ReportStatus status : newStatuses) { + if (!statusList.contains(status)) { + statusList.add(status); + added += 1; + } + } + if (added > 0) { + notifyItemRangeInserted(end, added); + } + } + + String[] getCheckedStatusIds() { + List idList = new ArrayList<>(); + for (ReportStatus status : statusList) { + if (status.checked) { + idList.add(status.id); + } + } + return idList.toArray(new String[0]); + } + + private static class ReportStatusViewHolder extends RecyclerView.ViewHolder { + private TextView content; + private CheckBox checkBox; + + ReportStatusViewHolder(View view) { + super(view); + content = (TextView) view.findViewById(R.id.report_status_content); + checkBox = (CheckBox) view.findViewById(R.id.report_status_check_box); + } + + void setupWithStatus(final ReportStatus status) { + content.setText(status.content); + checkBox.setChecked(status.checked); + checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + status.checked = isChecked; + } + }); + } + } +} diff --git a/app/src/main/res/drawable/report_status_divider_dark.xml b/app/src/main/res/drawable/report_status_divider_dark.xml new file mode 100644 index 000000000..ebfe26468 --- /dev/null +++ b/app/src/main/res/drawable/report_status_divider_dark.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/report_status_divider_light.xml b/app/src/main/res/drawable/report_status_divider_light.xml new file mode 100644 index 000000000..033c2448d --- /dev/null +++ b/app/src/main/res/drawable/report_status_divider_light.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_report.xml b/app/src/main/res/layout/activity_report.xml index 9d1d925b3..18e58158a 100644 --- a/app/src/main/res/layout/activity_report.xml +++ b/app/src/main/res/layout/activity_report.xml @@ -1,6 +1,7 @@ - - - diff --git a/app/src/main/res/layout/item_report_status.xml b/app/src/main/res/layout/item_report_status.xml new file mode 100644 index 000000000..6f9ab2557 --- /dev/null +++ b/app/src/main/res/layout/item_report_status.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 2e0404523..ea9532639 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -33,5 +33,6 @@ + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index e8a344c24..769877952 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -37,6 +37,7 @@ #9F9F9F #CFCFCF #000000 + #2F2F2F #44A673 #2C996E @@ -71,4 +72,5 @@ #7F7F7F #1F1F1F #EFEFEF + #9F9F9F diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8e526cfc4..62b88c6ca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -59,6 +59,7 @@ Couldn\'t fetch that thread. Failed to obtain that account. The report could not be sent. + At least one status must be reported. Home Notifications diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 18e42c1a5..974097923 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -50,6 +50,7 @@ @color/notification_content_faded_dark @color/notification_icon_tint_dark @color/report_status_background_dark + @drawable/report_status_divider_dark