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 @@ + + + + + + + + + + + + + +