Reorganizes the whole codebase.

This commit is contained in:
Vavassor 2017-05-04 18:55:34 -04:00
parent bdca1d1c94
commit aa2394748c
70 changed files with 1012 additions and 138 deletions

View File

@ -21,12 +21,17 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.text.Spanned; import android.text.Spanned;
import android.util.ArraySet;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.keylesspalace.tusky.entity.Notification; import com.keylesspalace.tusky.entity.Notification;
import com.keylesspalace.tusky.json.SpannedTypeAdapter;
import com.keylesspalace.tusky.json.StringWithEmoji;
import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter;
import com.keylesspalace.tusky.network.MastodonAPI;
import com.keylesspalace.tusky.util.NotificationMaker;
import com.keylesspalace.tusky.util.OkHttpUtils;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;

View File

@ -79,11 +79,11 @@
android:name="com.theartofdev.edmodo.cropper.CropImageActivity" android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:theme="@style/Base.Theme.AppCompat" /> android:theme="@style/Base.Theme.AppCompat" />
<receiver android:name=".NotificationClearBroadcastReceiver" /> <receiver android:name=".util.NotificationClearBroadcastReceiver" />
<service <service
tools:targetApi="24" tools:targetApi="24"
android:name="com.keylesspalace.tusky.TuskyTileService" android:name="com.keylesspalace.tusky.service.TuskyTileService"
android:icon="@drawable/ic_send_24dp" android:icon="@drawable/ic_send_24dp"
android:label="Compose Toot" android:label="Compose Toot"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">

View File

@ -23,6 +23,7 @@ import android.content.SharedPreferences;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.AttrRes; import android.support.annotation.AttrRes;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.design.widget.AppBarLayout; import android.support.design.widget.AppBarLayout;
@ -43,6 +44,14 @@ import android.widget.TextView;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.Relationship; import com.keylesspalace.tusky.entity.Relationship;
import com.keylesspalace.tusky.fragment.SFragment;
import com.keylesspalace.tusky.interfaces.LinkListener;
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
import com.keylesspalace.tusky.pager.AccountPagerAdapter;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.util.Assert;
import com.keylesspalace.tusky.util.Log;
import com.keylesspalace.tusky.util.ThemeUtils;
import com.pkmmte.view.CircularImageView; import com.pkmmte.view.CircularImageView;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
@ -237,7 +246,9 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
displayName.setText(account.getDisplayName()); displayName.setText(account.getDisplayName());
LinkHelper.setClickableText(note, account.note, null, new LinkListener() { boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("customTabs", true);
LinkHelper.setClickableText(note, account.note, null, useCustomTabs, new LinkListener() {
@Override @Override
public void onViewTag(String tag) { public void onViewTag(String tag) {
Intent intent = new Intent(AccountActivity.this, ViewTagActivity.class); Intent intent = new Intent(AccountActivity.this, ViewTagActivity.class);

View File

@ -24,6 +24,8 @@ import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
import com.keylesspalace.tusky.fragment.AccountListFragment;
public class AccountListActivity extends BaseActivity { public class AccountListActivity extends BaseActivity {
enum Type { enum Type {
BLOCKS, BLOCKS,

View File

@ -34,6 +34,13 @@ import android.view.Menu;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.keylesspalace.tusky.json.SpannedTypeAdapter;
import com.keylesspalace.tusky.json.StringWithEmoji;
import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter;
import com.keylesspalace.tusky.network.MastodonAPI;
import com.keylesspalace.tusky.network.TuskyAPI;
import com.keylesspalace.tusky.util.Log;
import com.keylesspalace.tusky.util.OkHttpUtils;
import java.io.IOException; import java.io.IOException;
@ -51,7 +58,7 @@ import retrofit2.converter.gson.GsonConverterFactory;
public class BaseActivity extends AppCompatActivity { public class BaseActivity extends AppCompatActivity {
private static final String TAG = "BaseActivity"; // logging tag private static final String TAG = "BaseActivity"; // logging tag
protected MastodonAPI mastodonAPI; public MastodonAPI mastodonAPI;
protected TuskyAPI tuskyAPI; protected TuskyAPI tuskyAPI;
protected Dispatcher mastodonApiDispatcher; protected Dispatcher mastodonApiDispatcher;
protected PendingIntent serviceAlarmIntent; protected PendingIntent serviceAlarmIntent;

View File

@ -71,6 +71,14 @@ import android.widget.TextView;
import com.keylesspalace.tusky.entity.Media; import com.keylesspalace.tusky.entity.Media;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.fragment.ComposeOptionsFragment;
import com.keylesspalace.tusky.util.DownsizeImageTask;
import com.keylesspalace.tusky.util.EditTextTyped;
import com.keylesspalace.tusky.util.CountUpDownLatch;
import com.keylesspalace.tusky.util.IOUtils;
import com.keylesspalace.tusky.util.Log;
import com.keylesspalace.tusky.util.SpanUtils;
import com.keylesspalace.tusky.util.ThemeUtils;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
@ -119,7 +127,8 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
private Uri photoUploadUri; private Uri photoUploadUri;
// this only exists when a status is trying to be sent, but uploads are still occurring // this only exists when a status is trying to be sent, but uploads are still occurring
private ProgressDialog finishingUploadDialog; private ProgressDialog finishingUploadDialog;
@BindView(R.id.compose_edit_field) EditTextTyped textEditor; @BindView(R.id.compose_edit_field)
EditTextTyped textEditor;
@BindView(R.id.compose_media_preview_bar) LinearLayout mediaPreviewBar; @BindView(R.id.compose_media_preview_bar) LinearLayout mediaPreviewBar;
@BindView(R.id.compose_content_warning_bar) View contentWarningBar; @BindView(R.id.compose_content_warning_bar) View contentWarningBar;
@BindView(R.id.field_content_warning) EditText contentWarningEditor; @BindView(R.id.field_content_warning) EditText contentWarningEditor;

View File

@ -36,7 +36,6 @@ import android.util.Base64;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
@ -44,6 +43,8 @@ import android.widget.ProgressBar;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.Profile; import com.keylesspalace.tusky.entity.Profile;
import com.keylesspalace.tusky.util.IOUtils;
import com.keylesspalace.tusky.util.Log;
import com.pkmmte.view.CircularImageView; import com.pkmmte.view.CircularImageView;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import com.theartofdev.edmodo.cropper.CropImage; import com.theartofdev.edmodo.cropper.CropImage;

View File

@ -23,6 +23,10 @@ import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
import com.keylesspalace.tusky.fragment.SFragment;
import com.keylesspalace.tusky.fragment.TimelineFragment;
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
public class FavouritesActivity extends BaseActivity implements SFragment.OnUserRemovedListener { public class FavouritesActivity extends BaseActivity implements SFragment.OnUserRemovedListener {
private StatusRemoveListener statusRemoveListener; private StatusRemoveListener statusRemoveListener;

View File

@ -36,6 +36,10 @@ import android.widget.TextView;
import com.keylesspalace.tusky.entity.AccessToken; import com.keylesspalace.tusky.entity.AccessToken;
import com.keylesspalace.tusky.entity.AppCredentials; import com.keylesspalace.tusky.entity.AppCredentials;
import com.keylesspalace.tusky.network.MastodonAPI;
import com.keylesspalace.tusky.util.CustomTabsHelper;
import com.keylesspalace.tusky.util.Log;
import com.keylesspalace.tusky.util.OkHttpUtils;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;

View File

@ -42,6 +42,11 @@ import com.arlib.floatingsearchview.FloatingSearchView;
import com.arlib.floatingsearchview.suggestions.SearchSuggestionsAdapter; import com.arlib.floatingsearchview.suggestions.SearchSuggestionsAdapter;
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion; import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.fragment.SFragment;
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
import com.keylesspalace.tusky.pager.TimelinePagerAdapter;
import com.keylesspalace.tusky.util.Log;
import com.keylesspalace.tusky.util.ThemeUtils;
import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.materialdrawer.AccountHeader; import com.mikepenz.materialdrawer.AccountHeader;
import com.mikepenz.materialdrawer.AccountHeaderBuilder; import com.mikepenz.materialdrawer.AccountHeaderBuilder;
@ -82,7 +87,7 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
@BindView(R.id.tab_layout) TabLayout tabLayout; @BindView(R.id.tab_layout) TabLayout tabLayout;
@BindView(R.id.pager) ViewPager viewPager; @BindView(R.id.pager) ViewPager viewPager;
FloatingActionButton composeButton; public FloatingActionButton composeButton;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {

View File

@ -21,6 +21,8 @@ import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.keylesspalace.tusky.fragment.PreferencesFragment;
public class PreferencesActivity extends BaseActivity public class PreferencesActivity extends BaseActivity
implements SharedPreferences.OnSharedPreferenceChangeListener { implements SharedPreferences.OnSharedPreferenceChangeListener {
private boolean themeSwitched; private boolean themeSwitched;

View File

@ -30,7 +30,11 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.EditText; import android.widget.EditText;
import com.keylesspalace.tusky.adapter.ReportAdapter;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.util.HtmlUtils;
import com.keylesspalace.tusky.util.Log;
import com.keylesspalace.tusky.util.ThemeUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;

View File

@ -23,6 +23,10 @@ import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
import com.keylesspalace.tusky.fragment.SFragment;
import com.keylesspalace.tusky.fragment.TimelineFragment;
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;

View File

@ -25,6 +25,10 @@ import android.support.v7.widget.Toolbar;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import com.keylesspalace.tusky.fragment.SFragment;
import com.keylesspalace.tusky.fragment.ViewThreadFragment;
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
public class ViewThreadActivity extends BaseActivity implements SFragment.OnUserRemovedListener { public class ViewThreadActivity extends BaseActivity implements SFragment.OnUserRemovedListener {
Fragment viewThreadFragment; Fragment viewThreadFragment;

View File

@ -13,17 +13,18 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.adapter;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
abstract class AccountAdapter extends RecyclerView.Adapter { public abstract class AccountAdapter extends RecyclerView.Adapter {
List<Account> accountList; List<Account> accountList;
AccountActionListener accountActionListener; AccountActionListener accountActionListener;
@ -38,7 +39,7 @@ abstract class AccountAdapter extends RecyclerView.Adapter {
return accountList.size() + 1; return accountList.size() + 1;
} }
void update(List<Account> newAccounts) { public void update(List<Account> newAccounts) {
if (newAccounts == null || newAccounts.isEmpty()) { if (newAccounts == null || newAccounts.isEmpty()) {
return; return;
} }
@ -59,14 +60,14 @@ abstract class AccountAdapter extends RecyclerView.Adapter {
notifyDataSetChanged(); notifyDataSetChanged();
} }
void addItems(List<Account> newAccounts) { public void addItems(List<Account> newAccounts) {
int end = accountList.size(); int end = accountList.size();
accountList.addAll(newAccounts); accountList.addAll(newAccounts);
notifyItemRangeInserted(end, newAccounts.size()); notifyItemRangeInserted(end, newAccounts.size());
} }
@Nullable @Nullable
Account removeItem(int position) { public Account removeItem(int position) {
if (position < 0 || position >= accountList.size()) { if (position < 0 || position >= accountList.size()) {
return null; return null;
} }
@ -75,7 +76,7 @@ abstract class AccountAdapter extends RecyclerView.Adapter {
return account; return account;
} }
void addItem(Account account, int position) { public void addItem(Account account, int position) {
if (position < 0 || position > accountList.size()) { if (position < 0 || position > accountList.size()) {
return; return;
} }

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.adapter;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -22,18 +22,20 @@ import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.pkmmte.view.CircularImageView; import com.pkmmte.view.CircularImageView;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
class BlocksAdapter extends AccountAdapter { public class BlocksAdapter extends AccountAdapter {
private static final int VIEW_TYPE_BLOCKED_USER = 0; private static final int VIEW_TYPE_BLOCKED_USER = 0;
private static final int VIEW_TYPE_FOOTER = 1; private static final int VIEW_TYPE_FOOTER = 1;
BlocksAdapter(AccountActionListener accountActionListener) { public BlocksAdapter(AccountActionListener accountActionListener) {
super(accountActionListener); super(accountActionListener);
} }

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.adapter;
import android.content.Context; import android.content.Context;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
@ -22,16 +22,18 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.pkmmte.view.CircularImageView; import com.pkmmte.view.CircularImageView;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
/** Both for follows and following lists. */ /** Both for follows and following lists. */
class FollowAdapter extends AccountAdapter { public class FollowAdapter extends AccountAdapter {
private static final int VIEW_TYPE_ACCOUNT = 0; private static final int VIEW_TYPE_ACCOUNT = 0;
private static final int VIEW_TYPE_FOOTER = 1; private static final int VIEW_TYPE_FOOTER = 1;
FollowAdapter(AccountActionListener accountActionListener) { public FollowAdapter(AccountActionListener accountActionListener) {
super(accountActionListener); super(accountActionListener);
} }

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.adapter;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -22,6 +22,8 @@ import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.pkmmte.view.CircularImageView; import com.pkmmte.view.CircularImageView;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
@ -29,11 +31,11 @@ import com.squareup.picasso.Picasso;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
class FollowRequestsAdapter extends AccountAdapter { public class FollowRequestsAdapter extends AccountAdapter {
private static final int VIEW_TYPE_FOLLOW_REQUEST = 0; private static final int VIEW_TYPE_FOLLOW_REQUEST = 0;
private static final int VIEW_TYPE_FOOTER = 1; private static final int VIEW_TYPE_FOOTER = 1;
FollowRequestsAdapter(AccountActionListener accountActionListener) { public FollowRequestsAdapter(AccountActionListener accountActionListener) {
super(accountActionListener); super(accountActionListener);
} }

View File

@ -13,12 +13,14 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.adapter;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.View; import android.view.View;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import com.keylesspalace.tusky.R;
class FooterViewHolder extends RecyclerView.ViewHolder { class FooterViewHolder extends RecyclerView.ViewHolder {
FooterViewHolder(View itemView) { FooterViewHolder(View itemView) {
super(itemView); super(itemView);

View File

@ -1,4 +1,4 @@
package com.keylesspalace.tusky; package com.keylesspalace.tusky.adapter;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -7,21 +7,20 @@ import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.pkmmte.view.CircularImageView; import com.pkmmte.view.CircularImageView;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import java.util.HashSet;
import java.util.Set;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
class MutesAdapter extends AccountAdapter { public class MutesAdapter extends AccountAdapter {
private static final int VIEW_TYPE_MUTED_USER = 0; private static final int VIEW_TYPE_MUTED_USER = 0;
private static final int VIEW_TYPE_FOOTER = 1; private static final int VIEW_TYPE_FOOTER = 1;
MutesAdapter(AccountActionListener accountActionListener) { public MutesAdapter(AccountActionListener accountActionListener) {
super(accountActionListener); super(accountActionListener);
} }

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.adapter;
import android.content.Context; import android.content.Context;
import android.graphics.Typeface; import android.graphics.Typeface;
@ -29,21 +29,24 @@ import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Notification; import com.keylesspalace.tusky.entity.Notification;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.AdapterItemRemover;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRemover { public class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
private static final int VIEW_TYPE_MENTION = 0; private static final int VIEW_TYPE_MENTION = 0;
private static final int VIEW_TYPE_FOOTER = 1; private static final int VIEW_TYPE_FOOTER = 1;
private static final int VIEW_TYPE_STATUS_NOTIFICATION = 2; private static final int VIEW_TYPE_STATUS_NOTIFICATION = 2;
private static final int VIEW_TYPE_FOLLOW = 3; private static final int VIEW_TYPE_FOLLOW = 3;
enum FooterState { public enum FooterState {
EMPTY, EMPTY,
END, END,
LOADING LOADING
@ -54,7 +57,7 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
private NotificationActionListener notificationActionListener; private NotificationActionListener notificationActionListener;
private FooterState footerState = FooterState.END; private FooterState footerState = FooterState.END;
NotificationsAdapter(StatusActionListener statusListener, public NotificationsAdapter(StatusActionListener statusListener,
NotificationActionListener notificationActionListener) { NotificationActionListener notificationActionListener) {
super(); super();
notifications = new ArrayList<>(); notifications = new ArrayList<>();
@ -63,7 +66,7 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
} }
void setFooterState(FooterState newFooterState) { public void setFooterState(FooterState newFooterState) {
FooterState oldValue = footerState; FooterState oldValue = footerState;
footerState = newFooterState; footerState = newFooterState;
if (footerState != oldValue) { if (footerState != oldValue) {
@ -179,7 +182,7 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
return null; return null;
} }
void update(List<Notification> newNotifications) { public void update(List<Notification> newNotifications) {
if (newNotifications == null || newNotifications.isEmpty()) { if (newNotifications == null || newNotifications.isEmpty()) {
return; return;
} }
@ -200,7 +203,7 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
notifyDataSetChanged(); notifyDataSetChanged();
} }
void addItems(List<Notification> new_notifications) { public void addItems(List<Notification> new_notifications) {
int end = notifications.size(); int end = notifications.size();
notifications.addAll(new_notifications); notifications.addAll(new_notifications);
notifyItemRangeInserted(end, new_notifications.size()); notifyItemRangeInserted(end, new_notifications.size());
@ -223,7 +226,7 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
} }
} }
interface NotificationActionListener { public interface NotificationActionListener {
void onViewAccount(String id); void onViewAccount(String id);
} }

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.adapter;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.text.Spanned; import android.text.Spanned;
@ -24,16 +24,18 @@ import android.widget.CheckBox;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.TextView; import android.widget.TextView;
import com.keylesspalace.tusky.R;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
class ReportAdapter extends RecyclerView.Adapter { public class ReportAdapter extends RecyclerView.Adapter {
static class ReportStatus { public static class ReportStatus {
String id; String id;
Spanned content; Spanned content;
boolean checked; boolean checked;
ReportStatus(String id, Spanned content, boolean checked) { public ReportStatus(String id, Spanned content, boolean checked) {
this.id = id; this.id = id;
this.content = content; this.content = content;
this.checked = checked; this.checked = checked;
@ -58,7 +60,7 @@ class ReportAdapter extends RecyclerView.Adapter {
private List<ReportStatus> statusList; private List<ReportStatus> statusList;
ReportAdapter() { public ReportAdapter() {
super(); super();
statusList = new ArrayList<>(); statusList = new ArrayList<>();
} }
@ -82,13 +84,13 @@ class ReportAdapter extends RecyclerView.Adapter {
return statusList.size(); return statusList.size();
} }
void addItem(ReportStatus status) { public void addItem(ReportStatus status) {
int end = statusList.size(); int end = statusList.size();
statusList.add(status); statusList.add(status);
notifyItemInserted(end); notifyItemInserted(end);
} }
void addItems(List<ReportStatus> newStatuses) { public void addItems(List<ReportStatus> newStatuses) {
int end = statusList.size(); int end = statusList.size();
int added = 0; int added = 0;
for (ReportStatus status : newStatuses) { for (ReportStatus status : newStatuses) {
@ -102,7 +104,7 @@ class ReportAdapter extends RecyclerView.Adapter {
} }
} }
String[] getCheckedStatusIds() { public String[] getCheckedStatusIds() {
List<String> idList = new ArrayList<>(); List<String> idList = new ArrayList<>();
for (ReportStatus status : statusList) { for (ReportStatus status : statusList) {
if (status.checked) { if (status.checked) {

View File

@ -13,9 +13,10 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.adapter;
import android.content.Context; import android.content.Context;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.text.Spanned; import android.text.Spanned;
@ -26,7 +27,13 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.ToggleButton; import android.widget.ToggleButton;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.util.RoundedTransformation;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.util.DateUtils;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.util.ThemeUtils;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import com.varunest.sparkbutton.SparkButton; import com.varunest.sparkbutton.SparkButton;
import com.varunest.sparkbutton.SparkEventListener; import com.varunest.sparkbutton.SparkEventListener;
@ -100,7 +107,10 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
StatusActionListener listener) { StatusActionListener listener) {
/* Redirect URLSpan's in the status content to the listener for viewing tag pages and /* Redirect URLSpan's in the status content to the listener for viewing tag pages and
* account pages. */ * account pages. */
LinkHelper.setClickableText(this.content, content, mentions, listener); Context context = this.content.getContext();
boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean("useCustomTabs", true);
LinkHelper.setClickableText(this.content, content, mentions, useCustomTabs, listener);
} }
private void setAvatar(String url) { private void setAvatar(String url) {

View File

@ -13,24 +13,27 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.adapter;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.interfaces.AdapterItemRemover;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover { public class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
private List<Status> statuses; private List<Status> statuses;
private StatusActionListener statusActionListener; private StatusActionListener statusActionListener;
private int statusIndex; private int statusIndex;
ThreadAdapter(StatusActionListener listener) { public ThreadAdapter(StatusActionListener listener) {
this.statusActionListener = listener; this.statusActionListener = listener;
this.statuses = new ArrayList<>(); this.statuses = new ArrayList<>();
this.statusIndex = 0; this.statusIndex = 0;
@ -55,7 +58,7 @@ class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
return statuses.size(); return statuses.size();
} }
Status getItem(int position) { public Status getItem(int position) {
return statuses.get(position); return statuses.get(position);
} }
@ -76,7 +79,7 @@ class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
} }
} }
int setStatus(Status status) { public int setStatus(Status status) {
if (statuses.size() > 0 && statuses.get(statusIndex).equals(status)) { if (statuses.size() > 0 && statuses.get(statusIndex).equals(status)) {
// Do not add this status on refresh, it's already in there. // Do not add this status on refresh, it's already in there.
statuses.set(statusIndex, status); statuses.set(statusIndex, status);
@ -88,7 +91,7 @@ class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
return i; return i;
} }
void setContext(List<Status> ancestors, List<Status> descendants) { public void setContext(List<Status> ancestors, List<Status> descendants) {
Status mainStatus = null; Status mainStatus = null;
// In case of refresh, remove old ancestors and descendants first. We'll remove all blindly, // In case of refresh, remove old ancestors and descendants first. We'll remove all blindly,

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.adapter;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
@ -21,16 +21,19 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.interfaces.AdapterItemRemover;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover { public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
private static final int VIEW_TYPE_STATUS = 0; private static final int VIEW_TYPE_STATUS = 0;
private static final int VIEW_TYPE_FOOTER = 1; private static final int VIEW_TYPE_FOOTER = 1;
enum FooterState { public enum FooterState {
EMPTY, EMPTY,
END, END,
LOADING LOADING
@ -40,7 +43,7 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
private StatusActionListener statusListener; private StatusActionListener statusListener;
private FooterState footerState = FooterState.END; private FooterState footerState = FooterState.END;
TimelineAdapter(StatusActionListener statusListener) { public TimelineAdapter(StatusActionListener statusListener) {
super(); super();
statuses = new ArrayList<>(); statuses = new ArrayList<>();
this.statusListener = statusListener; this.statusListener = statusListener;
@ -79,7 +82,7 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
} }
} }
void setFooterState(FooterState newFooterState) { public void setFooterState(FooterState newFooterState) {
FooterState oldValue = footerState; FooterState oldValue = footerState;
footerState = newFooterState; footerState = newFooterState;
if (footerState != oldValue) { if (footerState != oldValue) {
@ -110,7 +113,7 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
} }
} }
void update(List<Status> newStatuses) { public void update(List<Status> newStatuses) {
if (newStatuses == null || newStatuses.isEmpty()) { if (newStatuses == null || newStatuses.isEmpty()) {
return; return;
} }
@ -131,7 +134,7 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
notifyDataSetChanged(); notifyDataSetChanged();
} }
void addItems(List<Status> newStatuses) { public void addItems(List<Status> newStatuses) {
int end = statuses.size(); int end = statuses.size();
statuses.addAll(newStatuses); statuses.addAll(newStatuses);
notifyItemRangeInserted(end, newStatuses.size()); notifyItemRangeInserted(end, newStatuses.size());
@ -142,7 +145,7 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
notifyItemRemoved(position); notifyItemRemoved(position);
} }
void removeAllByAccountId(String accountId) { public void removeAllByAccountId(String accountId) {
for (int i = 0; i < statuses.size();) { for (int i = 0; i < statuses.size();) {
Status status = statuses.get(i); Status status = statuses.get(i);
if (accountId.equals(status.account.id)) { if (accountId.equals(status.account.id)) {
@ -155,7 +158,7 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
} }
@Nullable @Nullable
Status getItem(int position) { public Status getItem(int position) {
if (position >= 0 && position < statuses.size()) { if (position >= 0 && position < statuses.size()) {
return statuses.get(position); return statuses.get(position);
} }

View File

@ -20,8 +20,8 @@ import android.text.Spanned;
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion; import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import com.keylesspalace.tusky.HtmlUtils; import com.keylesspalace.tusky.util.HtmlUtils;
import com.keylesspalace.tusky.StringWithEmoji; import com.keylesspalace.tusky.json.StringWithEmoji;
public class Account implements SearchSuggestion { public class Account implements SearchSuggestion {
public String id; public String id;

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.fragment;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -29,8 +29,22 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.keylesspalace.tusky.AccountActivity;
import com.keylesspalace.tusky.adapter.AccountAdapter;
import com.keylesspalace.tusky.adapter.BlocksAdapter;
import com.keylesspalace.tusky.adapter.FollowAdapter;
import com.keylesspalace.tusky.adapter.FollowRequestsAdapter;
import com.keylesspalace.tusky.adapter.MutesAdapter;
import com.keylesspalace.tusky.BaseActivity;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.Relationship; import com.keylesspalace.tusky.entity.Relationship;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.keylesspalace.tusky.network.MastodonAPI;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.util.EndlessOnScrollListener;
import com.keylesspalace.tusky.util.Log;
import com.keylesspalace.tusky.util.ThemeUtils;
import java.util.List; import java.util.List;

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.fragment;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -21,6 +21,8 @@ import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import com.keylesspalace.tusky.R;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.fragment;
import android.content.Context; import android.content.Context;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
@ -33,8 +33,11 @@ import android.widget.CompoundButton;
import android.widget.RadioButton; import android.widget.RadioButton;
import android.widget.RadioGroup; import android.widget.RadioGroup;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.util.ThemeUtils;
public class ComposeOptionsFragment extends BottomSheetDialogFragment { public class ComposeOptionsFragment extends BottomSheetDialogFragment {
interface Listener { public interface Listener {
void onVisibilityChanged(String visibility); void onVisibilityChanged(String visibility);
void onContentWarningChanged(boolean hideText); void onContentWarningChanged(boolean hideText);
} }

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.fragment;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -31,8 +31,17 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.keylesspalace.tusky.MainActivity;
import com.keylesspalace.tusky.adapter.NotificationsAdapter;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Notification; import com.keylesspalace.tusky.entity.Notification;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
import com.keylesspalace.tusky.util.EndlessOnScrollListener;
import com.keylesspalace.tusky.util.Log;
import com.keylesspalace.tusky.util.ThemeUtils;
import java.util.List; import java.util.List;

View File

@ -13,11 +13,13 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.fragment;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import com.keylesspalace.tusky.R;
public class PreferencesFragment extends PreferenceFragment { public class PreferencesFragment extends PreferenceFragment {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.fragment;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -27,8 +27,19 @@ import android.text.Spanned;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import com.keylesspalace.tusky.AccountActivity;
import com.keylesspalace.tusky.BaseActivity;
import com.keylesspalace.tusky.ComposeActivity;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.ReportActivity;
import com.keylesspalace.tusky.ViewTagActivity;
import com.keylesspalace.tusky.ViewThreadActivity;
import com.keylesspalace.tusky.ViewVideoActivity;
import com.keylesspalace.tusky.entity.Relationship; import com.keylesspalace.tusky.entity.Relationship;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.AdapterItemRemover;
import com.keylesspalace.tusky.network.MastodonAPI;
import com.keylesspalace.tusky.util.HtmlUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -45,7 +56,7 @@ import retrofit2.Response;
* overlap functionality. So, I'm momentarily leaving it and hopefully working on those will clear * overlap functionality. So, I'm momentarily leaving it and hopefully working on those will clear
* up what needs to be where. */ * up what needs to be where. */
public abstract class SFragment extends BaseFragment { public abstract class SFragment extends BaseFragment {
interface OnUserRemovedListener { public interface OnUserRemovedListener {
void onUserRemoved(String accountId); void onUserRemoved(String accountId);
} }

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.fragment;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -31,7 +31,15 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.keylesspalace.tusky.MainActivity;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.adapter.TimelineAdapter;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
import com.keylesspalace.tusky.util.EndlessOnScrollListener;
import com.keylesspalace.tusky.util.Log;
import com.keylesspalace.tusky.util.ThemeUtils;
import java.util.List; import java.util.List;
@ -47,7 +55,7 @@ public class TimelineFragment extends SFragment implements
private Call<List<Status>> listCall; private Call<List<Status>> listCall;
enum Kind { public enum Kind {
HOME, HOME,
PUBLIC_LOCAL, PUBLIC_LOCAL,
PUBLIC_FEDERATED, PUBLIC_FEDERATED,

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.fragment;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.DownloadManager; import android.app.DownloadManager;
@ -36,6 +36,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import com.keylesspalace.tusky.R;
import com.squareup.picasso.Callback; import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.fragment;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
@ -29,8 +29,18 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.keylesspalace.tusky.adapter.ThreadAdapter;
import com.keylesspalace.tusky.BaseActivity;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.entity.StatusContext; import com.keylesspalace.tusky.entity.StatusContext;
import com.keylesspalace.tusky.network.MastodonAPI;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
import com.keylesspalace.tusky.util.ConversationLineItemDecoration;
import com.keylesspalace.tusky.util.Log;
import com.keylesspalace.tusky.util.ThemeUtils;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;

View File

@ -13,9 +13,9 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.interfaces;
interface AccountActionListener { public interface AccountActionListener {
void onViewAccount(String id); void onViewAccount(String id);
void onMute(final boolean mute, final String id, final int position); void onMute(final boolean mute, final String id, final int position);
void onBlock(final boolean block, final String id, final int position); void onBlock(final boolean block, final String id, final int position);

View File

@ -13,8 +13,8 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.interfaces;
interface AdapterItemRemover { public interface AdapterItemRemover {
void removeItem(int position); void removeItem(int position);
} }

View File

@ -13,9 +13,9 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.interfaces;
interface LinkListener { public interface LinkListener {
void onViewTag(String tag); void onViewTag(String tag);
void onViewAccount(String id); void onViewAccount(String id);
} }

View File

@ -13,13 +13,13 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.interfaces;
import android.view.View; import android.view.View;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;
interface StatusActionListener extends LinkListener { public interface StatusActionListener extends LinkListener {
void onReply(int position); void onReply(int position);
void onReblog(final boolean reblog, final int position); void onReblog(final boolean reblog, final int position);
void onFavourite(final boolean favourite, final int position); void onFavourite(final boolean favourite, final int position);

View File

@ -13,8 +13,8 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.interfaces;
interface StatusRemoveListener { public interface StatusRemoveListener {
void removePostsByUser(String accountId); void removePostsByUser(String accountId);
} }

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.json;
import android.text.Spanned; import android.text.Spanned;
@ -22,6 +22,7 @@ import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer; import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.keylesspalace.tusky.util.HtmlUtils;
import java.lang.reflect.Type; import java.lang.reflect.Type;

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.json;
/** /**
* This is just a wrapper class for a String. * This is just a wrapper class for a String.

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.json;
import com.emojione.Emojione; import com.emojione.Emojione;
import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializationContext;
@ -24,7 +24,7 @@ import com.google.gson.JsonParseException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
/** This is a type-based workaround to allow for shortcode conversion when loading display names. */ /** This is a type-based workaround to allow for shortcode conversion when loading display names. */
class StringWithEmojiTypeAdapter implements JsonDeserializer<StringWithEmoji> { public class StringWithEmojiTypeAdapter implements JsonDeserializer<StringWithEmoji> {
@Override @Override
public StringWithEmoji deserialize(JsonElement json, Type typeOfT, public StringWithEmoji deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException { JsonDeserializationContext context) throws JsonParseException {

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.network;
import com.keylesspalace.tusky.entity.AccessToken; import com.keylesspalace.tusky.entity.AccessToken;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.network;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
import retrofit2.Call; import retrofit2.Call;

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.pager;
import android.content.Context; import android.content.Context;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
@ -24,23 +24,27 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.fragment.AccountListFragment;
import com.keylesspalace.tusky.fragment.TimelineFragment;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
class AccountPagerAdapter extends FragmentPagerAdapter { public class AccountPagerAdapter extends FragmentPagerAdapter {
private Context context; private Context context;
private String accountId; private String accountId;
private String[] pageTitles; private String[] pageTitles;
private List<Fragment> registeredFragments; private List<Fragment> registeredFragments;
AccountPagerAdapter(FragmentManager manager, Context context, String accountId) { public AccountPagerAdapter(FragmentManager manager, Context context, String accountId) {
super(manager); super(manager);
this.context = context; this.context = context;
this.accountId = accountId; this.accountId = accountId;
registeredFragments = new ArrayList<>(); registeredFragments = new ArrayList<>();
} }
void setPageTitles(String[] titles) { public void setPageTitles(String[] titles) {
pageTitles = titles; pageTitles = titles;
} }
@ -72,7 +76,7 @@ class AccountPagerAdapter extends FragmentPagerAdapter {
return pageTitles[position]; return pageTitles[position];
} }
View getTabView(int position, ViewGroup root) { public View getTabView(int position, ViewGroup root) {
View view = LayoutInflater.from(context).inflate(R.layout.tab_account, root, false); View view = LayoutInflater.from(context).inflate(R.layout.tab_account, root, false);
TextView title = (TextView) view.findViewById(R.id.title); TextView title = (TextView) view.findViewById(R.id.title);
title.setText(pageTitles[position]); title.setText(pageTitles[position]);
@ -92,7 +96,7 @@ class AccountPagerAdapter extends FragmentPagerAdapter {
super.destroyItem(container, position, object); super.destroyItem(container, position, object);
} }
List<Fragment> getRegisteredFragments() { public List<Fragment> getRegisteredFragments() {
return registeredFragments; return registeredFragments;
} }
} }

View File

@ -13,31 +13,34 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.pager;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentPagerAdapter;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.keylesspalace.tusky.fragment.NotificationsFragment;
import com.keylesspalace.tusky.fragment.TimelineFragment;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
class TimelinePagerAdapter extends FragmentPagerAdapter { public class TimelinePagerAdapter extends FragmentPagerAdapter {
private int currentFragmentIndex; private int currentFragmentIndex;
private List<Fragment> registeredFragments; private List<Fragment> registeredFragments;
TimelinePagerAdapter(FragmentManager manager) { public TimelinePagerAdapter(FragmentManager manager) {
super(manager); super(manager);
currentFragmentIndex = 0; currentFragmentIndex = 0;
registeredFragments = new ArrayList<>(); registeredFragments = new ArrayList<>();
} }
Fragment getCurrentFragment() { public Fragment getCurrentFragment() {
return registeredFragments.get(currentFragmentIndex); return registeredFragments.get(currentFragmentIndex);
} }
List<Fragment> getRegisteredFragments() { public List<Fragment> getRegisteredFragments() {
return registeredFragments; return registeredFragments;
} }

View File

@ -13,12 +13,14 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.service;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Intent; import android.content.Intent;
import android.service.quicksettings.TileService; import android.service.quicksettings.TileService;
import com.keylesspalace.tusky.ComposeActivity;
/** /**
* Small Addition that adds in a QuickSettings tile that opens the Compose activity when clicked * Small Addition that adds in a QuickSettings tile that opens the Compose activity when clicked
* Created by ztepps on 4/3/17. * Created by ztepps on 4/3/17.

View File

@ -0,0 +1,29 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program 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 <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.util;
import com.keylesspalace.tusky.BuildConfig;
/** Android Studio complains about built-in assertions so this is an alternative. */
public class Assert {
private static boolean ENABLED = BuildConfig.DEBUG;
public static void expect(boolean expression) {
if (ENABLED && !expression) {
throw new AssertionError();
}
}
}

View File

@ -13,19 +13,17 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.util;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.util.TypedValue;
import android.view.View; import android.view.View;
import static android.util.TypedValue.COMPLEX_UNIT_DIP; import com.keylesspalace.tusky.R;
class ConversationLineItemDecoration extends RecyclerView.ItemDecoration { public class ConversationLineItemDecoration extends RecyclerView.ItemDecoration {
private final Context mContext; private final Context mContext;
private final Drawable mDivider; private final Drawable mDivider;

View File

@ -0,0 +1,40 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program 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 <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.util;
public class CountUpDownLatch {
private int count;
public CountUpDownLatch() {
this.count = 0;
}
public synchronized void countDown() {
count--;
notifyAll();
}
public synchronized void countUp() {
count++;
notifyAll();
}
public synchronized void await() throws InterruptedException {
while (count != 0) {
wait();
}
}
}

View File

@ -1,4 +1,4 @@
package com.keylesspalace.tusky; package com.keylesspalace.tusky.util;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
@ -11,6 +11,8 @@ import android.support.v4.content.ContextCompat;
import android.text.style.URLSpan; import android.text.style.URLSpan;
import android.view.View; import android.view.View;
import com.keylesspalace.tusky.R;
class CustomTabURLSpan extends URLSpan { class CustomTabURLSpan extends URLSpan {
CustomTabURLSpan(String url) { CustomTabURLSpan(String url) {
super(url); super(url);

View File

@ -1,4 +1,4 @@
package com.keylesspalace.tusky; package com.keylesspalace.tusky.util;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;

View File

@ -0,0 +1,50 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program 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 <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.util;
public class DateUtils {
/* This is a rough duplicate of android.text.format.DateUtils.getRelativeTimeSpanString,
* but even with the FORMAT_ABBREV_RELATIVE flag it wasn't abbreviating enough. */
public static String getRelativeTimeSpanString(long then, long now) {
final long MINUTE = 60;
final long HOUR = 60 * MINUTE;
final long DAY = 24 * HOUR;
final long YEAR = 365 * DAY;
long span = (now - then) / 1000;
String prefix = "";
if (span < 0) {
prefix = "in ";
span = -span;
}
String unit;
if (span < MINUTE) {
unit = "s";
} else if (span < HOUR) {
span /= MINUTE;
unit = "m";
} else if (span < DAY) {
span /= HOUR;
unit = "h";
} else if (span < YEAR) {
span /= DAY;
unit = "d";
} else {
span /= YEAR;
unit = "y";
}
return prefix + span + unit;
}
}

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.util;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -31,13 +31,13 @@ import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> { public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
private int sizeLimit; private int sizeLimit;
private ContentResolver contentResolver; private ContentResolver contentResolver;
private Listener listener; private Listener listener;
private List<byte[]> resultList; private List<byte[]> resultList;
DownsizeImageTask(int sizeLimit, ContentResolver contentResolver, Listener listener) { public DownsizeImageTask(int sizeLimit, ContentResolver contentResolver, Listener listener) {
this.sizeLimit = sizeLimit; this.sizeLimit = sizeLimit;
this.contentResolver = contentResolver; this.contentResolver = contentResolver;
this.listener = listener; this.listener = listener;
@ -219,7 +219,7 @@ class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
super.onPostExecute(successful); super.onPostExecute(successful);
} }
interface Listener { public interface Listener {
void onSuccess(List<byte[]> contentList); void onSuccess(List<byte[]> contentList);
void onFailure(); void onFailure();
} }

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.util;
import android.content.Context; import android.content.Context;
import android.support.v13.view.inputmethod.EditorInfoCompat; import android.support.v13.view.inputmethod.EditorInfoCompat;

View File

@ -13,12 +13,12 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.util;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
abstract class EndlessOnScrollListener extends RecyclerView.OnScrollListener { public abstract class EndlessOnScrollListener extends RecyclerView.OnScrollListener {
private static final int VISIBLE_THRESHOLD = 15; private static final int VISIBLE_THRESHOLD = 15;
private int currentPage; private int currentPage;
private int previousTotalItemCount; private int previousTotalItemCount;
@ -26,7 +26,7 @@ abstract class EndlessOnScrollListener extends RecyclerView.OnScrollListener {
private int startingPageIndex; private int startingPageIndex;
private LinearLayoutManager layoutManager; private LinearLayoutManager layoutManager;
EndlessOnScrollListener(LinearLayoutManager layoutManager) { public EndlessOnScrollListener(LinearLayoutManager layoutManager) {
this.layoutManager = layoutManager; this.layoutManager = layoutManager;
currentPage = 0; currentPage = 0;
previousTotalItemCount = 0; previousTotalItemCount = 0;
@ -56,7 +56,7 @@ abstract class EndlessOnScrollListener extends RecyclerView.OnScrollListener {
} }
} }
void reset() { public void reset() {
currentPage = startingPageIndex; currentPage = startingPageIndex;
previousTotalItemCount = 0; previousTotalItemCount = 0;
loading = true; loading = true;

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.util;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
@ -21,6 +21,8 @@ import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.keylesspalace.tusky.R;
public class FlowLayout extends ViewGroup { public class FlowLayout extends ViewGroup {
private int paddingHorizontal; // internal padding between child views private int paddingHorizontal; // internal padding between child views
private int paddingVertical; // private int paddingVertical; //

View File

@ -0,0 +1,54 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program 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 <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.util;
import android.os.Build;
import android.text.Html;
import android.text.Spanned;
public class HtmlUtils {
private static CharSequence trimTrailingWhitespace(CharSequence s) {
int i = s.length();
do {
i--;
} while (i >= 0 && Character.isWhitespace(s.charAt(i)));
return s.subSequence(0, i + 1);
}
@SuppressWarnings("deprecation")
public static Spanned fromHtml(String html) {
Spanned result;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
} else {
result = Html.fromHtml(html);
}
/* Html.fromHtml returns trailing whitespace if the html ends in a </p> tag, which
* all status contents do, so it should be trimmed. */
return (Spanned) trimTrailingWhitespace(result);
}
@SuppressWarnings("deprecation")
public static String toHtml(Spanned text) {
String result;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
result = Html.toHtml(text, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE);
} else {
result = Html.toHtml(text);
}
return result;
}
}

View File

@ -0,0 +1,44 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program 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 <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.util;
import android.support.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class IOUtils {
public static void closeQuietly(@Nullable InputStream stream) {
try {
if (stream != null) {
stream.close();
}
} catch (IOException e) {
// intentionally unhandled
}
}
public static void closeQuietly(@Nullable OutputStream stream) {
try {
if (stream != null) {
stream.close();
}
} catch (IOException e) {
// intentionally unhandled
}
}
}

View File

@ -13,9 +13,8 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.util;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
@ -26,14 +25,13 @@ import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.LinkListener;
class LinkHelper { public class LinkHelper {
static void setClickableText(TextView view, Spanned content, public static void setClickableText(TextView view, Spanned content,
@Nullable Status.Mention[] mentions, @Nullable Status.Mention[] mentions, boolean useCustomTabs,
final LinkListener listener) { final LinkListener listener) {
SpannableStringBuilder builder = new SpannableStringBuilder(content); SpannableStringBuilder builder = new SpannableStringBuilder(content);
boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(view.getContext())
.getBoolean("customTabs", true);
URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class); URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class);
for (URLSpan span : urlSpans) { for (URLSpan span : urlSpans) {
int start = builder.getSpanStart(span); int start = builder.getSpanStart(span);

View File

@ -0,0 +1,53 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program 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 <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.util;
import com.keylesspalace.tusky.BuildConfig;
/**A wrapper for android.util.Log that allows for disabling logging, such as for release builds.*/
public class Log {
private static final boolean LOGGING_ENABLED = BuildConfig.DEBUG;
public static void i(String tag, String string) {
if (LOGGING_ENABLED) {
android.util.Log.i(tag, string);
}
}
public static void e(String tag, String string) {
if (LOGGING_ENABLED) {
android.util.Log.e(tag, string);
}
}
public static void d(String tag, String string) {
if (LOGGING_ENABLED) {
android.util.Log.d(tag, string);
}
}
public static void v(String tag, String string) {
if (LOGGING_ENABLED) {
android.util.Log.v(tag, string);
}
}
public static void w(String tag, String string) {
if (LOGGING_ENABLED) {
android.util.Log.w(tag, string);
}
}
}

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.util;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.util;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -29,6 +29,8 @@ import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder; import android.support.v4.app.TaskStackBuilder;
import com.keylesspalace.tusky.MainActivity;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Notification; import com.keylesspalace.tusky.entity.Notification;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target; import com.squareup.picasso.Target;
@ -36,8 +38,8 @@ import com.squareup.picasso.Target;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
class NotificationMaker { public class NotificationMaker {
static void make(final Context context, final int notifyId, Notification body) { public static void make(final Context context, final int notifyId, Notification body) {
final SharedPreferences preferences = final SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(context); PreferenceManager.getDefaultSharedPreferences(context);
final SharedPreferences notificationPreferences = context.getSharedPreferences( final SharedPreferences notificationPreferences = context.getSharedPreferences(

View File

@ -0,0 +1,244 @@
/* 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
* Lesser 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 Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with Tusky. If
* not, see <http://www.gnu.org/licenses/>. */
package com.keylesspalace.tusky.util;
import android.os.Build;
import android.support.annotation.NonNull;
import com.keylesspalace.tusky.BuildConfig;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.ConnectionSpec;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class OkHttpUtils {
static final String TAG = "OkHttpUtils"; // logging tag
/**
* Makes a Builder with the maximum range of TLS versions and cipher suites enabled.
*
* It first tries the "approved" list of cipher suites given in OkHttp (the default in
* ConnectionSpec.MODERN_TLS) and if that doesn't work falls back to the set of ALL enabled,
* then falls back to plain http.
*
* API level 24 has a regression in elliptic curves where it only supports secp256r1, so this
* first tries a fallback without elliptic curves at all, and then tries them after.
*
* TLS 1.1 and 1.2 have to be manually enabled on API levels 16-20.
*/
@NonNull
public static OkHttpClient.Builder getCompatibleClientBuilder() {
ConnectionSpec fallback = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.allEnabledCipherSuites()
.supportsTlsExtensions(true)
.build();
List<ConnectionSpec> specList = new ArrayList<>();
specList.add(ConnectionSpec.MODERN_TLS);
addNougatFixConnectionSpec(specList);
specList.add(fallback);
specList.add(ConnectionSpec.CLEARTEXT);
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.addInterceptor(getUserAgentInterceptor())
.connectionSpecs(specList);
return enableHigherTlsOnPreLollipop(builder);
}
@NonNull
public static OkHttpClient getCompatibleClient() {
return getCompatibleClientBuilder().build();
}
/**
* Add a custom User-Agent that contains Tusky & Android Version to all requests
* Example:
* User-Agent: Tusky/1.1.2 Android/5.0.2
*/
@NonNull
private static Interceptor getUserAgentInterceptor() {
return new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request requestWithUserAgent = originalRequest.newBuilder()
.header("User-Agent", "Tusky/"+ BuildConfig.VERSION_NAME+" Android/"+Build.VERSION.RELEASE)
.build();
return chain.proceed(requestWithUserAgent);
}
};
}
/**
* Android version Nougat has a regression where elliptic curve cipher suites are supported, but
* only the curve secp256r1 is allowed. So, first it's best to just disable all elliptic
* ciphers, try the connection, and fall back to the all cipher suites enabled list after.
*/
private static void addNougatFixConnectionSpec(List<ConnectionSpec> specList) {
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.N) {
return;
}
SSLSocketFactory socketFactory;
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { trustManager }, null);
socketFactory = sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException|KeyStoreException|KeyManagementException e) {
Log.e(TAG, "Failed obtaining the SSL socket factory.");
return;
}
String[] cipherSuites = socketFactory.getDefaultCipherSuites();
ArrayList<String> allowedList = new ArrayList<>();
for (String suite : cipherSuites) {
if (!suite.contains("ECDH")) {
allowedList.add(suite);
}
}
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.cipherSuites(allowedList.toArray(new String[0]))
.supportsTlsExtensions(true)
.build();
specList.add(spec);
}
private static OkHttpClient.Builder enableHigherTlsOnPreLollipop(OkHttpClient.Builder builder) {
if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22) {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { trustManager }, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
builder.sslSocketFactory(new SSLSocketFactoryCompat(sslSocketFactory),
trustManager);
} catch (NoSuchAlgorithmException|KeyStoreException|KeyManagementException e) {
Log.e(TAG, "Failed enabling TLS 1.1 & 1.2. " + e.getMessage());
}
}
return builder;
}
private static class SSLSocketFactoryCompat extends SSLSocketFactory {
private static final String[] DESIRED_TLS_VERSIONS = { "TLSv1", "TLSv1.1", "TLSv1.2",
"TLSv1.3" };
final SSLSocketFactory delegate;
SSLSocketFactoryCompat(SSLSocketFactory base) {
this.delegate = base;
}
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose)
throws IOException {
return patch(delegate.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException {
return patch(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
throws IOException {
return patch(delegate.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return patch(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
int localPort) throws IOException {
return patch(delegate.createSocket(address, port, localAddress, localPort));
}
@NonNull
private static String[] getMatches(String[] wanted, String[] have) {
List<String> a = new ArrayList<>(Arrays.asList(wanted));
List<String> b = Arrays.asList(have);
a.retainAll(b);
return a.toArray(new String[0]);
}
private Socket patch(Socket socket) {
if (socket instanceof SSLSocket) {
SSLSocket sslSocket = (SSLSocket) socket;
String[] protocols = getMatches(DESIRED_TLS_VERSIONS,
sslSocket.getSupportedProtocols());
sslSocket.setEnabledProtocols(protocols);
}
return socket;
}
}
}

View File

@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky; package com.keylesspalace.tusky.util;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapShader; import android.graphics.BitmapShader;

View File

@ -0,0 +1,129 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program 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 <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.util;
import android.text.Spannable;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
public class SpanUtils {
private static class FindCharsResult {
int charIndex;
int stringIndex;
FindCharsResult() {
charIndex = -1;
stringIndex = -1;
}
}
private static FindCharsResult findChars(String string, int fromIndex, char[] chars) {
FindCharsResult result = new FindCharsResult();
final int length = string.length();
for (int i = fromIndex; i < length; i++) {
char c = string.charAt(i);
for (int j = 0; j < chars.length; j++) {
if (chars[j] == c) {
result.charIndex = j;
result.stringIndex = i;
return result;
}
}
}
return result;
}
private static FindCharsResult findStart(String string, int fromIndex, char[] chars) {
final int length = string.length();
while (fromIndex < length) {
FindCharsResult found = findChars(string, fromIndex, chars);
int i = found.stringIndex;
if (i < 0) {
break;
} else if (i == 0 || i >= 1 && Character.isWhitespace(string.codePointBefore(i))) {
return found;
} else {
fromIndex = i + 1;
}
}
return new FindCharsResult();
}
private static int findEndOfHashtag(String string, int fromIndex) {
final int length = string.length();
for (int i = fromIndex + 1; i < length;) {
int codepoint = string.codePointAt(i);
if (Character.isWhitespace(codepoint)) {
return i;
} else if (codepoint == '#') {
return -1;
}
i += Character.charCount(codepoint);
}
return length;
}
private static int findEndOfMention(String string, int fromIndex) {
int atCount = 0;
final int length = string.length();
for (int i = fromIndex + 1; i < length;) {
int codepoint = string.codePointAt(i);
if (Character.isWhitespace(codepoint)) {
return i;
} else if (codepoint == '@') {
atCount += 1;
if (atCount >= 2) {
return -1;
}
}
i += Character.charCount(codepoint);
}
return length;
}
public static void highlightSpans(Spannable text, int colour) {
// Strip all existing colour spans.
int n = text.length();
ForegroundColorSpan[] oldSpans = text.getSpans(0, n, ForegroundColorSpan.class);
for (int i = oldSpans.length - 1; i >= 0; i--) {
text.removeSpan(oldSpans[i]);
}
// Colour the mentions and hashtags.
String string = text.toString();
int start;
int end = 0;
while (end < n) {
char[] chars = { '#', '@' };
FindCharsResult found = findStart(string, end, chars);
start = found.stringIndex;
if (start < 0) {
break;
}
if (found.charIndex == 0) {
end = findEndOfHashtag(string, start);
} else if (found.charIndex == 1) {
end = findEndOfMention(string, start);
} else {
break;
}
if (end < 0) {
break;
}
text.setSpan(new ForegroundColorSpan(colour), start, end,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
}

View File

@ -0,0 +1,68 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program 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 <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.util;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.support.annotation.AttrRes;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.v4.content.ContextCompat;
import android.util.TypedValue;
import android.widget.ImageView;
public class ThemeUtils {
public static Drawable getDrawable(Context context, @AttrRes int attribute,
@DrawableRes int fallbackDrawable) {
TypedValue value = new TypedValue();
@DrawableRes int resourceId;
if (context.getTheme().resolveAttribute(attribute, value, true)) {
resourceId = value.resourceId;
} else {
resourceId = fallbackDrawable;
}
return ContextCompat.getDrawable(context, resourceId);
}
public 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;
} else {
return fallbackDrawableId;
}
}
public static @ColorInt int getColor(Context context, @AttrRes int attribute) {
TypedValue value = new TypedValue();
if (context.getTheme().resolveAttribute(attribute, value, true)) {
return value.data;
} else {
return Color.BLACK;
}
}
public static void setImageViewTint(ImageView view, @AttrRes int attribute) {
view.setColorFilter(getColor(view.getContext(), attribute), PorterDuff.Mode.SRC_IN);
}
public static void setDrawableTint(Context context, Drawable drawable, @AttrRes int attribute) {
drawable.setColorFilter(getColor(context, attribute), PorterDuff.Mode.SRC_IN);
}
}

View File

@ -54,7 +54,7 @@
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp"> android:paddingRight="16dp">
<com.keylesspalace.tusky.EditTextTyped <com.keylesspalace.tusky.util.EditTextTyped
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/compose_edit_field" android:id="@+id/compose_edit_field"

View File

@ -95,7 +95,7 @@
</RelativeLayout> </RelativeLayout>
<com.keylesspalace.tusky.FlowLayout <com.keylesspalace.tusky.util.FlowLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/status_content_warning_bar" android:id="@+id/status_content_warning_bar"
@ -127,7 +127,7 @@
android:textAllCaps="true" android:textAllCaps="true"
android:background="?attr/content_warning_button" /> android:background="?attr/content_warning_button" />
</com.keylesspalace.tusky.FlowLayout> </com.keylesspalace.tusky.util.FlowLayout>
<TextView <TextView
android:id="@+id/status_content" android:id="@+id/status_content"