diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7491beee..2601bee2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -39,6 +39,9 @@
+
= Build.VERSION_CODES.N) {
+ result = Html.toHtml(text, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE);
+ } else {
+ result = Html.toHtml(text);
+ }
+ return result;
+ }
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java b/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java
new file mode 100644
index 00000000..b7620341
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java
@@ -0,0 +1,158 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is part of Tusky.
+ *
+ * Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tusky. If not, see
+ * . */
+
+package com.keylesspalace.tusky;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+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.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.JsonObjectRequest;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ReportActivity extends BaseActivity {
+ private static final String TAG = "ReportActivity"; // Volley request tag
+
+ private String domain;
+ private String accessToken;
+ private View anyView; // what Snackbar will use to find the root view
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_report);
+
+ Intent intent = getIntent();
+ final String accountId = intent.getStringExtra("account_id");
+ String accountUsername = intent.getStringExtra("account_username");
+ final String statusId = intent.getStringExtra("status_id");
+ String statusContent = intent.getStringExtra("status_content");
+
+ SharedPreferences preferences = getSharedPreferences(
+ getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
+ domain = preferences.getString("domain", null);
+ accessToken = preferences.getString("accessToken", null);
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ ActionBar bar = getSupportActionBar();
+ if (bar != null) {
+ String title = String.format(getString(R.string.report_username_format),
+ accountUsername);
+ bar.setTitle(title);
+ }
+ anyView = toolbar;
+
+ TextView content = (TextView) findViewById(R.id.report_status_content);
+ content.setText(HtmlUtils.fromHtml(statusContent));
+
+ 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());
+ }
+ });
+ }
+
+ /* JSONArray has a constructor to take primitive arrays but it's restricted to API level 19 and
+ * above, so this is an alternative. */
+ private static JSONArray makeStringArrayCompat(String[] stringArray) throws JSONException {
+ JSONArray result = new JSONArray();
+ for (int i = 0; i < stringArray.length; i++) {
+ result.put(i, stringArray[i]);
+ }
+ return result;
+ }
+
+ private void sendReport(final String accountId, final String[] statusIds,
+ final String comment) {
+ JSONObject parameters = new JSONObject();
+ try {
+ parameters.put("account_id", accountId);
+ 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());
+ onSendFailure(accountId, statusIds, comment);
+ return;
+ }
+ String endpoint = getString(R.string.endpoint_reports);
+ String url = "https://" + domain + endpoint;
+ JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, url, parameters,
+ new Response.Listener() {
+ @Override
+ public void onResponse(JSONObject response) {
+ onSendSuccess();
+ }
+ },
+ new Response.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ onSendFailure(accountId, statusIds, comment);
+ }
+ }) {
+ @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 onSendSuccess() {
+ Toast.makeText(this, getString(R.string.confirmation_reported), Toast.LENGTH_SHORT)
+ .show();
+ finish();
+ }
+
+ private void onSendFailure(final String accountId, final String[] statusIds,
+ final String comment) {
+ Snackbar.make(anyView, R.string.error_report_unsent, Snackbar.LENGTH_LONG)
+ .setAction(R.string.action_retry, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ sendReport(accountId, statusIds, comment);
+ }
+ })
+ .show();
+ }
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/SFragment.java
index e3f4cb40..73bafe8b 100644
--- a/app/src/main/java/com/keylesspalace/tusky/SFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/SFragment.java
@@ -24,6 +24,7 @@ import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.RecyclerView;
+import android.text.Spanned;
import android.view.MenuItem;
import android.view.View;
@@ -180,6 +181,8 @@ public class SFragment extends Fragment {
final int position) {
final String id = status.getId();
final String accountId = status.getAccountId();
+ final String accountUsename = status.getUsername();
+ final Spanned content = status.getContent();
PopupMenu popup = new PopupMenu(getContext(), view);
// Give a different menu depending on whether this is the user's own toot or not.
if (loggedInAccountId == null || !loggedInAccountId.equals(accountId)) {
@@ -200,6 +203,10 @@ public class SFragment extends Fragment {
block(accountId);
return true;
}
+ case R.id.status_report: {
+ openReportPage(accountId, accountUsename, id, content);
+ return true;
+ }
case R.id.status_delete: {
delete(id);
adapter.removeItem(position);
@@ -266,4 +273,14 @@ public class SFragment extends Fragment {
intent.putExtra("username", username);
startActivity(intent);
}
+
+ protected void openReportPage(String accountId, String accoundUsername, String statusId,
+ Spanned statusContent) {
+ Intent intent = new Intent(getContext(), ReportActivity.class);
+ intent.putExtra("account_id", accountId);
+ intent.putExtra("account_username", accoundUsername);
+ intent.putExtra("status_id", statusId);
+ intent.putExtra("status_content", HtmlUtils.toHtml(statusContent));
+ startActivity(intent);
+ }
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/ThemeUtils.java b/app/src/main/java/com/keylesspalace/tusky/ThemeUtils.java
index 1d3cb82e..01f5476f 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ThemeUtils.java
+++ b/app/src/main/java/com/keylesspalace/tusky/ThemeUtils.java
@@ -18,14 +18,18 @@ package com.keylesspalace.tusky;
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
+import android.support.annotation.AttrRes;
+import android.support.annotation.ColorRes;
+import android.support.annotation.DrawableRes;
import android.support.v4.content.ContextCompat;
import android.util.TypedValue;
import android.widget.ImageView;
class ThemeUtils {
- static Drawable getDrawable(Context context, int attribute, int fallbackDrawable) {
+ static Drawable getDrawable(Context context, @AttrRes int attribute,
+ @DrawableRes int fallbackDrawable) {
TypedValue value = new TypedValue();
- int resourceId;
+ @DrawableRes int resourceId;
if (context.getTheme().resolveAttribute(attribute, value, true)) {
resourceId = value.resourceId;
} else {
@@ -34,7 +38,8 @@ class ThemeUtils {
return ContextCompat.getDrawable(context, resourceId);
}
- static int getDrawableId(Context context, int attribute, int fallbackDrawableId) {
+ static @DrawableRes int getDrawableId(Context context, @AttrRes int attribute,
+ @DrawableRes int fallbackDrawableId) {
TypedValue value = new TypedValue();
if (context.getTheme().resolveAttribute(attribute, value, true)) {
return value.resourceId;
@@ -43,7 +48,7 @@ class ThemeUtils {
}
}
- static int getColor(Context context, int attribute) {
+ static @ColorRes int getColor(Context context, @AttrRes int attribute) {
TypedValue value = new TypedValue();
if (context.getTheme().resolveAttribute(attribute, value, true)) {
return value.data;
@@ -52,7 +57,7 @@ class ThemeUtils {
}
}
- static void setImageViewTint(ImageView view, int attribute) {
+ static void setImageViewTint(ImageView view, @AttrRes int attribute) {
view.setColorFilter(getColor(view.getContext(), attribute), PorterDuff.Mode.SRC_IN);
}
}
diff --git a/app/src/main/res/drawable/border_background_dark.xml b/app/src/main/res/drawable/border_background_dark.xml
new file mode 100644
index 00000000..ce373def
--- /dev/null
+++ b/app/src/main/res/drawable/border_background_dark.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/border_background.xml b/app/src/main/res/drawable/border_background_light.xml
similarity index 100%
rename from app/src/main/res/drawable/border_background.xml
rename to app/src/main/res/drawable/border_background_light.xml
diff --git a/app/src/main/res/layout/activity_compose.xml b/app/src/main/res/layout/activity_compose.xml
index e8d732ee..b063c158 100644
--- a/app/src/main/res/layout/activity_compose.xml
+++ b/app/src/main/res/layout/activity_compose.xml
@@ -33,7 +33,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/compose_content_warning_bar"
- android:background="@drawable/border_background"
android:layout_margin="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp">
diff --git a/app/src/main/res/layout/activity_report.xml b/app/src/main/res/layout/activity_report.xml
new file mode 100644
index 00000000..9d1d925b
--- /dev/null
+++ b/app/src/main/res/layout/activity_report.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/status_more.xml b/app/src/main/res/menu/status_more.xml
index 018f574d..6886ea86 100644
--- a/app/src/main/res/menu/status_more.xml
+++ b/app/src/main/res/menu/status_more.xml
@@ -5,4 +5,6 @@
android:title="@string/action_follow" />
+
\ 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 f1a25182..2e040452 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -31,5 +31,7 @@
+
+
\ 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 3aaf8769..e8a344c2 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -36,6 +36,7 @@
#AFBFCF
#9F9F9F
#CFCFCF
+ #000000
#44A673
#2C996E
@@ -69,4 +70,5 @@
#2F5F6F
#7F7F7F
#1F1F1F
+ #EFEFEF
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9630f67e..8e526cfc 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -35,6 +35,7 @@
/api/v1/accounts/%s/unfollow
/api/v1/accounts/%s/block
/api/v1/accounts/%s/unblock
+ /api/v1/reports
/api/v1/apps
/oauth/authorize
/oauth/token
@@ -57,6 +58,7 @@
That user wasn\'t unblocked.
Couldn\'t fetch that thread.
Failed to obtain that account.
+ The report could not be sent.
Home
Notifications
@@ -87,6 +89,9 @@
%s favourited your status
%s followed you
+ Reporting @%s
+ Additional Comments?
+
Compose
Ask Site To Log In
Log Out
@@ -94,6 +99,7 @@
Unfollow
Block
Unblock
+ Report
Delete
TOOT
Retry
@@ -111,6 +117,7 @@
Set
Toot!
+ Sent!
Which Site?
What\'s Happening?
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 86cc48ff..18e42c1a 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -46,8 +46,10 @@
- @color/compose_media_button_dark
- @color/compose_media_button_disabled_dark
- @color/compose_mention_dark
+ - @drawable/border_background_dark
- @color/notification_content_faded_dark
- @color/notification_icon_tint_dark
+ - @color/report_status_background_dark