The reporting page now allows reporting multiple statuses. (It's a bit cramped, though.)

This commit is contained in:
Vavassor 2017-02-28 21:38:47 -05:00
parent 4b8573a82f
commit 3ab06eb250
10 changed files with 256 additions and 17 deletions

View file

@ -18,28 +18,34 @@ package com.keylesspalace.tusky;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v7.app.ActionBar; 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.support.v7.widget.Toolbar;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.android.volley.AuthFailureError; import com.android.volley.AuthFailureError;
import com.android.volley.Request; import com.android.volley.Request;
import com.android.volley.Response; import com.android.volley.Response;
import com.android.volley.VolleyError; import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.JsonObjectRequest; import com.android.volley.toolbox.JsonObjectRequest;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
public class ReportActivity extends BaseActivity { public class ReportActivity extends BaseActivity {
@ -48,6 +54,7 @@ public class ReportActivity extends BaseActivity {
private String domain; private String domain;
private String accessToken; private String accessToken;
private View anyView; // what Snackbar will use to find the root view private View anyView; // what Snackbar will use to find the root view
private ReportAdapter adapter;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -57,7 +64,7 @@ public class ReportActivity extends BaseActivity {
Intent intent = getIntent(); Intent intent = getIntent();
final String accountId = intent.getStringExtra("account_id"); final String accountId = intent.getStringExtra("account_id");
String accountUsername = intent.getStringExtra("account_username"); 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"); String statusContent = intent.getStringExtra("status_content");
SharedPreferences preferences = getSharedPreferences( SharedPreferences preferences = getSharedPreferences(
@ -75,18 +82,39 @@ public class ReportActivity extends BaseActivity {
} }
anyView = toolbar; anyView = toolbar;
TextView content = (TextView) findViewById(R.id.report_status_content); final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.report_recycler_view);
content.setText(HtmlUtils.fromHtml(statusContent)); 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); final EditText comment = (EditText) findViewById(R.id.report_comment);
Button send = (Button) findViewById(R.id.report_send); Button send = (Button) findViewById(R.id.report_send);
send.setOnClickListener(new View.OnClickListener() { send.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
String[] statusIds = new String[] { statusId }; String[] statusIds = adapter.getCheckedStatusIds();
sendReport(accountId, statusIds, comment.getText().toString()); 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 /* 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("status_ids", makeStringArrayCompat(statusIds));
parameters.put("comment", comment); parameters.put("comment", comment);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, "Not all the report parameters have been properly set. " Log.e(TAG, "Not all the report parameters have been properly set. " + e.getMessage());
+ e.getMessage());
onSendFailure(accountId, statusIds, comment); onSendFailure(accountId, statusIds, comment);
return; return;
} }
@ -155,4 +182,51 @@ public class ReportActivity extends BaseActivity {
}) })
.show(); .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<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
List<Status> statusList;
try {
statusList = Status.parse(response);
} catch (JSONException e) {
onFetchStatusesFailure(e);
return;
}
// Add all the statuses except reblogs.
List<ReportAdapter.ReportStatus> 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<String, String> getHeaders() throws AuthFailureError {
Map<String, String> 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());
}
} }

View file

@ -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<ReportStatus> 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<ReportStatus> 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<String> 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;
}
});
}
}
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="1dp" />
<solid android:color="@color/report_status_divider_dark" />
</shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="1dp" />
<solid android:color="@color/report_status_divider_light" />
</shape>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent" android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<android.support.v7.widget.Toolbar <android.support.v7.widget.Toolbar
@ -10,11 +11,13 @@
android:elevation="4dp" android:elevation="4dp"
android:background="?attr/toolbar_background_color" /> android:background="?attr/toolbar_background_color" />
<TextView <android.support.v7.widget.RecyclerView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:id="@+id/report_status_content" android:layout_weight="2"
android:padding="8dp" android:id="@+id/report_recycler_view"
android:scrollbars="vertical"
android:fadeScrollbars="false"
android:background="?attr/report_status_background_color" /> android:background="?attr/report_status_background_color" />
<EditText <EditText
@ -29,10 +32,6 @@
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:hint="@string/report_comment_hint" /> android:hint="@string/report_comment_hint" />
<Space
android:layout_width="match_parent"
android:layout_height="8dp" />
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/report_status_content"
android:layout_weight="1"
android:padding="8dp" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/report_status_check_box"
android:layout_centerVertical="true"
android:layout_margin="16dp" />
</RelativeLayout>
</LinearLayout>

View file

@ -33,5 +33,6 @@
<attr name="compose_mention_color" format="reference|color" /> <attr name="compose_mention_color" format="reference|color" />
<attr name="compose_content_warning_bar_background" format="reference" /> <attr name="compose_content_warning_bar_background" format="reference" />
<attr name="report_status_background_color" format="reference|color" /> <attr name="report_status_background_color" format="reference|color" />
<attr name="report_status_divider_drawable" format="reference" />
</resources> </resources>

View file

@ -37,6 +37,7 @@
<color name="notification_content_faded_dark">#9F9F9F</color> <color name="notification_content_faded_dark">#9F9F9F</color>
<color name="notification_icon_tint_dark">#CFCFCF</color> <color name="notification_icon_tint_dark">#CFCFCF</color>
<color name="report_status_background_dark">#000000</color> <color name="report_status_background_dark">#000000</color>
<color name="report_status_divider_dark">#2F2F2F</color>
<!--Light Theme Colors--> <!--Light Theme Colors-->
<color name="color_primary_light">#44A673</color> <color name="color_primary_light">#44A673</color>
<color name="color_primary_dark_light">#2C996E</color> <color name="color_primary_dark_light">#2C996E</color>
@ -71,4 +72,5 @@
<color name="notification_content_faded_light">#7F7F7F</color> <color name="notification_content_faded_light">#7F7F7F</color>
<color name="notification_icon_tint_light">#1F1F1F</color> <color name="notification_icon_tint_light">#1F1F1F</color>
<color name="report_status_background_light">#EFEFEF</color> <color name="report_status_background_light">#EFEFEF</color>
<color name="report_status_divider_light">#9F9F9F</color>
</resources> </resources>

View file

@ -59,6 +59,7 @@
<string name="error_view_thread">Couldn\'t fetch that thread.</string> <string name="error_view_thread">Couldn\'t fetch that thread.</string>
<string name="error_obtain_account">Failed to obtain that account.</string> <string name="error_obtain_account">Failed to obtain that account.</string>
<string name="error_report_unsent">The report could not be sent.</string> <string name="error_report_unsent">The report could not be sent.</string>
<string name="error_report_too_few_statuses">At least one status must be reported.</string>
<string name="title_home">Home</string> <string name="title_home">Home</string>
<string name="title_notifications">Notifications</string> <string name="title_notifications">Notifications</string>

View file

@ -50,6 +50,7 @@
<item name="notification_content">@color/notification_content_faded_dark</item> <item name="notification_content">@color/notification_content_faded_dark</item>
<item name="notification_icon_tint">@color/notification_icon_tint_dark</item> <item name="notification_icon_tint">@color/notification_icon_tint_dark</item>
<item name="report_status_background_color">@color/report_status_background_dark</item> <item name="report_status_background_color">@color/report_status_background_dark</item>
<item name="report_status_divider_drawable">@drawable/report_status_divider_dark</item>
</style> </style>
<style name="AppTheme.ImageButton.Dark" parent="@style/Widget.AppCompat.Button.Borderless.Colored"> <style name="AppTheme.ImageButton.Dark" parent="@style/Widget.AppCompat.Button.Borderless.Colored">
@ -103,6 +104,7 @@
<item name="notification_content">@color/notification_content_faded_light</item> <item name="notification_content">@color/notification_content_faded_light</item>
<item name="notification_icon_tint">@color/notification_icon_tint_light</item> <item name="notification_icon_tint">@color/notification_icon_tint_light</item>
<item name="report_status_background_color">@color/report_status_background_light</item> <item name="report_status_background_color">@color/report_status_background_light</item>
<item name="report_status_divider_drawable">@drawable/report_status_divider_light</item>
</style> </style>
<style name="AppTheme.ImageButton.Light" parent="Widget.AppCompat.Button.Borderless.Colored"> <style name="AppTheme.ImageButton.Light" parent="Widget.AppCompat.Button.Borderless.Colored">