Reorganizes the whole codebase.
This commit is contained in:
parent
bdca1d1c94
commit
aa2394748c
70 changed files with 1012 additions and 138 deletions
|
@ -0,0 +1,93 @@
|
|||
/* 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.adapter;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.interfaces.AccountActionListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AccountAdapter extends RecyclerView.Adapter {
|
||||
List<Account> accountList;
|
||||
AccountActionListener accountActionListener;
|
||||
|
||||
AccountAdapter(AccountActionListener accountActionListener) {
|
||||
super();
|
||||
accountList = new ArrayList<>();
|
||||
this.accountActionListener = accountActionListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return accountList.size() + 1;
|
||||
}
|
||||
|
||||
public void update(List<Account> newAccounts) {
|
||||
if (newAccounts == null || newAccounts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (accountList.isEmpty()) {
|
||||
accountList = newAccounts;
|
||||
} else {
|
||||
int index = accountList.indexOf(newAccounts.get(newAccounts.size() - 1));
|
||||
for (int i = 0; i < index; i++) {
|
||||
accountList.remove(0);
|
||||
}
|
||||
int newIndex = newAccounts.indexOf(accountList.get(0));
|
||||
if (newIndex == -1) {
|
||||
accountList.addAll(0, newAccounts);
|
||||
} else {
|
||||
accountList.addAll(0, newAccounts.subList(0, newIndex));
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void addItems(List<Account> newAccounts) {
|
||||
int end = accountList.size();
|
||||
accountList.addAll(newAccounts);
|
||||
notifyItemRangeInserted(end, newAccounts.size());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Account removeItem(int position) {
|
||||
if (position < 0 || position >= accountList.size()) {
|
||||
return null;
|
||||
}
|
||||
Account account = accountList.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
return account;
|
||||
}
|
||||
|
||||
public void addItem(Account account, int position) {
|
||||
if (position < 0 || position > accountList.size()) {
|
||||
return;
|
||||
}
|
||||
accountList.add(position, account);
|
||||
notifyItemInserted(position);
|
||||
}
|
||||
|
||||
public Account getItem(int position) {
|
||||
if (position >= 0 && position < accountList.size()) {
|
||||
return accountList.get(position);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/* 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.adapter;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.interfaces.AccountActionListener;
|
||||
import com.pkmmte.view.CircularImageView;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class BlocksAdapter extends AccountAdapter {
|
||||
private static final int VIEW_TYPE_BLOCKED_USER = 0;
|
||||
private static final int VIEW_TYPE_FOOTER = 1;
|
||||
|
||||
public BlocksAdapter(AccountActionListener accountActionListener) {
|
||||
super(accountActionListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
default:
|
||||
case VIEW_TYPE_BLOCKED_USER: {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_blocked_user, parent, false);
|
||||
return new BlockedUserViewHolder(view);
|
||||
}
|
||||
case VIEW_TYPE_FOOTER: {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_footer, parent, false);
|
||||
return new FooterViewHolder(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||
if (position < accountList.size()) {
|
||||
BlockedUserViewHolder holder = (BlockedUserViewHolder) viewHolder;
|
||||
holder.setupWithAccount(accountList.get(position));
|
||||
holder.setupActionListener(accountActionListener, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == accountList.size()) {
|
||||
return VIEW_TYPE_FOOTER;
|
||||
} else {
|
||||
return VIEW_TYPE_BLOCKED_USER;
|
||||
}
|
||||
}
|
||||
|
||||
static class BlockedUserViewHolder extends RecyclerView.ViewHolder {
|
||||
@BindView(R.id.blocked_user_avatar) CircularImageView avatar;
|
||||
@BindView(R.id.blocked_user_username) TextView username;
|
||||
@BindView(R.id.blocked_user_display_name) TextView displayName;
|
||||
@BindView(R.id.blocked_user_unblock) ImageButton unblock;
|
||||
|
||||
private String id;
|
||||
|
||||
BlockedUserViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
}
|
||||
|
||||
void setupWithAccount(Account account) {
|
||||
id = account.id;
|
||||
displayName.setText(account.getDisplayName());
|
||||
String format = username.getContext().getString(R.string.status_username_format);
|
||||
String formattedUsername = String.format(format, account.username);
|
||||
username.setText(formattedUsername);
|
||||
Picasso.with(avatar.getContext())
|
||||
.load(account.avatar)
|
||||
.error(R.drawable.avatar_error)
|
||||
.placeholder(R.drawable.avatar_default)
|
||||
.into(avatar);
|
||||
}
|
||||
|
||||
void setupActionListener(final AccountActionListener listener, final boolean blocked) {
|
||||
unblock.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onBlock(!blocked, id, position);
|
||||
}
|
||||
}
|
||||
});
|
||||
avatar.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onViewAccount(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/* 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.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.interfaces.AccountActionListener;
|
||||
import com.pkmmte.view.CircularImageView;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
/** Both for follows and following lists. */
|
||||
public class FollowAdapter extends AccountAdapter {
|
||||
private static final int VIEW_TYPE_ACCOUNT = 0;
|
||||
private static final int VIEW_TYPE_FOOTER = 1;
|
||||
|
||||
public FollowAdapter(AccountActionListener accountActionListener) {
|
||||
super(accountActionListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
default:
|
||||
case VIEW_TYPE_ACCOUNT: {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_account, parent, false);
|
||||
return new AccountViewHolder(view);
|
||||
}
|
||||
case VIEW_TYPE_FOOTER: {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_footer, parent, false);
|
||||
return new FooterViewHolder(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||
if (position < accountList.size()) {
|
||||
AccountViewHolder holder = (AccountViewHolder) viewHolder;
|
||||
holder.setupWithAccount(accountList.get(position));
|
||||
holder.setupActionListener(accountActionListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == accountList.size()) {
|
||||
return VIEW_TYPE_FOOTER;
|
||||
} else {
|
||||
return VIEW_TYPE_ACCOUNT;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AccountViewHolder extends RecyclerView.ViewHolder {
|
||||
private View container;
|
||||
private TextView username;
|
||||
private TextView displayName;
|
||||
private CircularImageView avatar;
|
||||
private String id;
|
||||
|
||||
AccountViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
container = itemView.findViewById(R.id.account_container);
|
||||
username = (TextView) itemView.findViewById(R.id.account_username);
|
||||
displayName = (TextView) itemView.findViewById(R.id.account_display_name);
|
||||
avatar = (CircularImageView) itemView.findViewById(R.id.account_avatar);
|
||||
}
|
||||
|
||||
void setupWithAccount(Account account) {
|
||||
id = account.id;
|
||||
String format = username.getContext().getString(R.string.status_username_format);
|
||||
String formattedUsername = String.format(format, account.username);
|
||||
username.setText(formattedUsername);
|
||||
displayName.setText(account.getDisplayName());
|
||||
Context context = avatar.getContext();
|
||||
Picasso.with(context)
|
||||
.load(account.avatar)
|
||||
.placeholder(R.drawable.avatar_default)
|
||||
.error(R.drawable.avatar_error)
|
||||
.into(avatar);
|
||||
}
|
||||
|
||||
void setupActionListener(final AccountActionListener listener) {
|
||||
container.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onViewAccount(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/* 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.adapter;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keylesspalace.tusky.interfaces.AccountActionListener;
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.pkmmte.view.CircularImageView;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class FollowRequestsAdapter extends AccountAdapter {
|
||||
private static final int VIEW_TYPE_FOLLOW_REQUEST = 0;
|
||||
private static final int VIEW_TYPE_FOOTER = 1;
|
||||
|
||||
public FollowRequestsAdapter(AccountActionListener accountActionListener) {
|
||||
super(accountActionListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
default:
|
||||
case VIEW_TYPE_FOLLOW_REQUEST: {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_follow_request, parent, false);
|
||||
return new FollowRequestViewHolder(view);
|
||||
}
|
||||
case VIEW_TYPE_FOOTER: {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_footer, parent, false);
|
||||
return new FooterViewHolder(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||
if (position < accountList.size()) {
|
||||
FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
|
||||
holder.setupWithAccount(accountList.get(position));
|
||||
holder.setupActionListener(accountActionListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == accountList.size()) {
|
||||
return VIEW_TYPE_FOOTER;
|
||||
} else {
|
||||
return VIEW_TYPE_FOLLOW_REQUEST;
|
||||
}
|
||||
}
|
||||
|
||||
static class FollowRequestViewHolder extends RecyclerView.ViewHolder {
|
||||
@BindView(R.id.follow_request_avatar) CircularImageView avatar;
|
||||
@BindView(R.id.follow_request_username) TextView username;
|
||||
@BindView(R.id.follow_request_display_name) TextView displayName;
|
||||
@BindView(R.id.follow_request_accept) ImageButton accept;
|
||||
@BindView(R.id.follow_request_reject) ImageButton reject;
|
||||
|
||||
private String id;
|
||||
|
||||
FollowRequestViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
}
|
||||
|
||||
void setupWithAccount(Account account) {
|
||||
id = account.id;
|
||||
displayName.setText(account.getDisplayName());
|
||||
String format = username.getContext().getString(R.string.status_username_format);
|
||||
String formattedUsername = String.format(format, account.username);
|
||||
username.setText(formattedUsername);
|
||||
Picasso.with(avatar.getContext())
|
||||
.load(account.avatar)
|
||||
.error(R.drawable.avatar_error)
|
||||
.placeholder(R.drawable.avatar_default)
|
||||
.into(avatar);
|
||||
}
|
||||
|
||||
void setupActionListener(final AccountActionListener listener) {
|
||||
accept.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onRespondToFollowRequest(true, id, position);
|
||||
}
|
||||
}
|
||||
});
|
||||
reject.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onRespondToFollowRequest(false, id, position);
|
||||
}
|
||||
}
|
||||
});
|
||||
avatar.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onViewAccount(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/* 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.adapter;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
|
||||
class FooterViewHolder extends RecyclerView.ViewHolder {
|
||||
FooterViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ProgressBar progressBar = (ProgressBar) itemView.findViewById(R.id.footer_progress_bar);
|
||||
if (progressBar != null) {
|
||||
progressBar.setIndeterminate(true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package com.keylesspalace.tusky.adapter;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.interfaces.AccountActionListener;
|
||||
import com.pkmmte.view.CircularImageView;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class MutesAdapter extends AccountAdapter {
|
||||
private static final int VIEW_TYPE_MUTED_USER = 0;
|
||||
private static final int VIEW_TYPE_FOOTER = 1;
|
||||
|
||||
public MutesAdapter(AccountActionListener accountActionListener) {
|
||||
super(accountActionListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
default:
|
||||
case VIEW_TYPE_MUTED_USER: {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_muted_user, parent, false);
|
||||
return new MutesAdapter.MutedUserViewHolder(view);
|
||||
}
|
||||
case VIEW_TYPE_FOOTER: {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_footer, parent, false);
|
||||
return new FooterViewHolder(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||
if (position < accountList.size()) {
|
||||
MutedUserViewHolder holder = (MutedUserViewHolder) viewHolder;
|
||||
holder.setupWithAccount(accountList.get(position));
|
||||
holder.setupActionListener(accountActionListener, true, position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == accountList.size()) {
|
||||
return VIEW_TYPE_FOOTER;
|
||||
} else {
|
||||
return VIEW_TYPE_MUTED_USER;
|
||||
}
|
||||
}
|
||||
|
||||
static class MutedUserViewHolder extends RecyclerView.ViewHolder {
|
||||
@BindView(R.id.muted_user_avatar) CircularImageView avatar;
|
||||
@BindView(R.id.muted_user_username) TextView username;
|
||||
@BindView(R.id.muted_user_display_name) TextView displayName;
|
||||
@BindView(R.id.muted_user_unmute) ImageButton unmute;
|
||||
|
||||
private String id;
|
||||
|
||||
MutedUserViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
}
|
||||
|
||||
void setupWithAccount(Account account) {
|
||||
id = account.id;
|
||||
displayName.setText(account.getDisplayName());
|
||||
String format = username.getContext().getString(R.string.status_username_format);
|
||||
String formattedUsername = String.format(format, account.username);
|
||||
username.setText(formattedUsername);
|
||||
Picasso.with(avatar.getContext())
|
||||
.load(account.avatar)
|
||||
.error(R.drawable.avatar_error)
|
||||
.placeholder(R.drawable.avatar_default)
|
||||
.into(avatar);
|
||||
}
|
||||
|
||||
void setupActionListener(final AccountActionListener listener, final boolean muted,
|
||||
final int position) {
|
||||
unmute.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onMute(!muted, id, position);
|
||||
}
|
||||
});
|
||||
avatar.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onViewAccount(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,328 @@
|
|||
/* 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.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Notification;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.interfaces.AdapterItemRemover;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
|
||||
private static final int VIEW_TYPE_MENTION = 0;
|
||||
private static final int VIEW_TYPE_FOOTER = 1;
|
||||
private static final int VIEW_TYPE_STATUS_NOTIFICATION = 2;
|
||||
private static final int VIEW_TYPE_FOLLOW = 3;
|
||||
|
||||
public enum FooterState {
|
||||
EMPTY,
|
||||
END,
|
||||
LOADING
|
||||
}
|
||||
|
||||
private List<Notification> notifications;
|
||||
private StatusActionListener statusListener;
|
||||
private NotificationActionListener notificationActionListener;
|
||||
private FooterState footerState = FooterState.END;
|
||||
|
||||
public NotificationsAdapter(StatusActionListener statusListener,
|
||||
NotificationActionListener notificationActionListener) {
|
||||
super();
|
||||
notifications = new ArrayList<>();
|
||||
this.statusListener = statusListener;
|
||||
this.notificationActionListener = notificationActionListener;
|
||||
}
|
||||
|
||||
|
||||
public void setFooterState(FooterState newFooterState) {
|
||||
FooterState oldValue = footerState;
|
||||
footerState = newFooterState;
|
||||
if (footerState != oldValue) {
|
||||
notifyItemChanged(notifications.size());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
default:
|
||||
case VIEW_TYPE_MENTION: {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_status, parent, false);
|
||||
return new StatusViewHolder(view);
|
||||
}
|
||||
case VIEW_TYPE_FOOTER: {
|
||||
View view;
|
||||
switch (footerState) {
|
||||
default:
|
||||
case LOADING:
|
||||
view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_footer, parent, false);
|
||||
break;
|
||||
case END: {
|
||||
view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_footer_end, parent, false);
|
||||
break;
|
||||
}
|
||||
case EMPTY: {
|
||||
view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_footer_empty, parent, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new FooterViewHolder(view);
|
||||
}
|
||||
case VIEW_TYPE_STATUS_NOTIFICATION: {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_status_notification, parent, false);
|
||||
return new StatusNotificationViewHolder(view);
|
||||
}
|
||||
case VIEW_TYPE_FOLLOW: {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_follow, parent, false);
|
||||
return new FollowViewHolder(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||
if (position < notifications.size()) {
|
||||
Notification notification = notifications.get(position);
|
||||
Notification.Type type = notification.type;
|
||||
switch (type) {
|
||||
case MENTION: {
|
||||
StatusViewHolder holder = (StatusViewHolder) viewHolder;
|
||||
Status status = notification.status;
|
||||
holder.setupWithStatus(status, statusListener);
|
||||
break;
|
||||
}
|
||||
case FAVOURITE:
|
||||
case REBLOG: {
|
||||
StatusNotificationViewHolder holder = (StatusNotificationViewHolder) viewHolder;
|
||||
holder.setMessage(type, notification.account.getDisplayName(),
|
||||
notification.status);
|
||||
holder.setupButtons(notificationActionListener, notification.account.id);
|
||||
break;
|
||||
}
|
||||
case FOLLOW: {
|
||||
FollowViewHolder holder = (FollowViewHolder) viewHolder;
|
||||
holder.setMessage(notification.account.getDisplayName(), notification.account.username,
|
||||
notification.account.avatar);
|
||||
holder.setupButtons(notificationActionListener, notification.account.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return notifications.size() + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == notifications.size()) {
|
||||
return VIEW_TYPE_FOOTER;
|
||||
} else {
|
||||
Notification notification = notifications.get(position);
|
||||
switch (notification.type) {
|
||||
default:
|
||||
case MENTION: {
|
||||
return VIEW_TYPE_MENTION;
|
||||
}
|
||||
case FAVOURITE:
|
||||
case REBLOG: {
|
||||
return VIEW_TYPE_STATUS_NOTIFICATION;
|
||||
}
|
||||
case FOLLOW: {
|
||||
return VIEW_TYPE_FOLLOW;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Notification getItem(int position) {
|
||||
if (position >= 0 && position < notifications.size()) {
|
||||
return notifications.get(position);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void update(List<Notification> newNotifications) {
|
||||
if (newNotifications == null || newNotifications.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (notifications.isEmpty()) {
|
||||
notifications = newNotifications;
|
||||
} else {
|
||||
int index = notifications.indexOf(newNotifications.get(newNotifications.size() - 1));
|
||||
for (int i = 0; i < index; i++) {
|
||||
notifications.remove(0);
|
||||
}
|
||||
int newIndex = newNotifications.indexOf(notifications.get(0));
|
||||
if (newIndex == -1) {
|
||||
notifications.addAll(0, newNotifications);
|
||||
} else {
|
||||
notifications.addAll(0, newNotifications.subList(0, newIndex));
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void addItems(List<Notification> new_notifications) {
|
||||
int end = notifications.size();
|
||||
notifications.addAll(new_notifications);
|
||||
notifyItemRangeInserted(end, new_notifications.size());
|
||||
}
|
||||
|
||||
public void removeItem(int position) {
|
||||
notifications.remove(position);
|
||||
notifyItemChanged(position);
|
||||
}
|
||||
|
||||
public void removeAllByAccountId(String id) {
|
||||
for (int i = 0; i < notifications.size();) {
|
||||
Notification notification = notifications.get(i);
|
||||
if (id.equals(notification.account.id)) {
|
||||
notifications.remove(i);
|
||||
notifyItemRemoved(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface NotificationActionListener {
|
||||
void onViewAccount(String id);
|
||||
}
|
||||
|
||||
private static class FollowViewHolder extends RecyclerView.ViewHolder {
|
||||
private TextView message;
|
||||
private TextView usernameView;
|
||||
private TextView displayNameView;
|
||||
private ImageView avatar;
|
||||
|
||||
FollowViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
message = (TextView) itemView.findViewById(R.id.notification_text);
|
||||
usernameView = (TextView) itemView.findViewById(R.id.notification_username);
|
||||
displayNameView = (TextView) itemView.findViewById(R.id.notification_display_name);
|
||||
avatar = (ImageView) itemView.findViewById(R.id.notification_avatar);
|
||||
}
|
||||
|
||||
void setMessage(String displayName, String username, String avatarUrl) {
|
||||
Context context = message.getContext();
|
||||
|
||||
String format = context.getString(R.string.notification_follow_format);
|
||||
String wholeMessage = String.format(format, displayName);
|
||||
message.setText(wholeMessage);
|
||||
|
||||
format = context.getString(R.string.status_username_format);
|
||||
String wholeUsername = String.format(format, username);
|
||||
usernameView.setText(wholeUsername);
|
||||
|
||||
displayNameView.setText(displayName);
|
||||
|
||||
Picasso.with(context)
|
||||
.load(avatarUrl)
|
||||
.placeholder(R.drawable.avatar_default)
|
||||
.error(R.drawable.avatar_error)
|
||||
.into(avatar);
|
||||
}
|
||||
|
||||
void setupButtons(final NotificationActionListener listener, final String accountId) {
|
||||
avatar.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onViewAccount(accountId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static class StatusNotificationViewHolder extends RecyclerView.ViewHolder {
|
||||
private TextView message;
|
||||
private ImageView icon;
|
||||
private TextView statusContent;
|
||||
private ViewGroup container;
|
||||
|
||||
StatusNotificationViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
message = (TextView) itemView.findViewById(R.id.notification_text);
|
||||
icon = (ImageView) itemView.findViewById(R.id.notification_icon);
|
||||
statusContent = (TextView) itemView.findViewById(R.id.notification_content);
|
||||
container = (ViewGroup) itemView.findViewById(R.id.notification_container);
|
||||
}
|
||||
|
||||
void setMessage(Notification.Type type, String displayName, Status status) {
|
||||
Context context = message.getContext();
|
||||
String format;
|
||||
switch (type) {
|
||||
default:
|
||||
case FAVOURITE: {
|
||||
icon.setImageResource(R.drawable.ic_star_24dp);
|
||||
icon.setColorFilter(ContextCompat.getColor(context,
|
||||
R.color.status_favourite_button_marked_dark));
|
||||
format = context.getString(R.string.notification_favourite_format);
|
||||
break;
|
||||
}
|
||||
case REBLOG: {
|
||||
icon.setImageResource(R.drawable.ic_repeat_24dp);
|
||||
icon.setColorFilter(ContextCompat.getColor(context,
|
||||
R.color.color_accent_dark));
|
||||
format = context.getString(R.string.notification_reblog_format);
|
||||
break;
|
||||
}
|
||||
}
|
||||
String wholeMessage = String.format(format, displayName);
|
||||
final SpannableStringBuilder str = new SpannableStringBuilder(wholeMessage);
|
||||
str.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
message.setText(str);
|
||||
statusContent.setText(status.content);
|
||||
}
|
||||
|
||||
void setupButtons(final NotificationActionListener listener, final String accountId) {
|
||||
container.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onViewAccount(accountId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/* 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.adapter;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Spanned;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ReportAdapter extends RecyclerView.Adapter {
|
||||
public static class ReportStatus {
|
||||
String id;
|
||||
Spanned content;
|
||||
boolean checked;
|
||||
|
||||
public ReportStatus(String id, Spanned content, boolean checked) {
|
||||
this.id = id;
|
||||
this.content = content;
|
||||
this.checked = checked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this.id == null) {
|
||||
return this == other;
|
||||
} else if (!(other instanceof ReportStatus)) {
|
||||
return false;
|
||||
}
|
||||
ReportStatus status = (ReportStatus) other;
|
||||
return status.id.equals(this.id);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ReportStatus> statusList;
|
||||
|
||||
public ReportAdapter() {
|
||||
super();
|
||||
statusList = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_report_status, parent, false);
|
||||
return new ReportStatusViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||
ReportStatusViewHolder holder = (ReportStatusViewHolder) viewHolder;
|
||||
ReportStatus status = statusList.get(position);
|
||||
holder.setupWithStatus(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return statusList.size();
|
||||
}
|
||||
|
||||
public void addItem(ReportStatus status) {
|
||||
int end = statusList.size();
|
||||
statusList.add(status);
|
||||
notifyItemInserted(end);
|
||||
}
|
||||
|
||||
public void addItems(List<ReportStatus> newStatuses) {
|
||||
int end = statusList.size();
|
||||
int added = 0;
|
||||
for (ReportStatus status : newStatuses) {
|
||||
if (!statusList.contains(status)) {
|
||||
statusList.add(status);
|
||||
added += 1;
|
||||
}
|
||||
}
|
||||
if (added > 0) {
|
||||
notifyItemRangeInserted(end, added);
|
||||
}
|
||||
}
|
||||
|
||||
public String[] getCheckedStatusIds() {
|
||||
List<String> idList = new ArrayList<>();
|
||||
for (ReportStatus status : statusList) {
|
||||
if (status.checked) {
|
||||
idList.add(status.id);
|
||||
}
|
||||
}
|
||||
return idList.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private static class ReportStatusViewHolder extends RecyclerView.ViewHolder {
|
||||
private TextView content;
|
||||
private CheckBox checkBox;
|
||||
|
||||
ReportStatusViewHolder(View view) {
|
||||
super(view);
|
||||
content = (TextView) view.findViewById(R.id.report_status_content);
|
||||
checkBox = (CheckBox) view.findViewById(R.id.report_status_check_box);
|
||||
}
|
||||
|
||||
void setupWithStatus(final ReportStatus status) {
|
||||
content.setText(status.content);
|
||||
checkBox.setChecked(status.checked);
|
||||
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
status.checked = isChecked;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,378 @@
|
|||
/* 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.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Spanned;
|
||||
import android.view.View;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
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.util.DateUtils;
|
||||
import com.keylesspalace.tusky.util.LinkHelper;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.varunest.sparkbutton.SparkButton;
|
||||
import com.varunest.sparkbutton.SparkEventListener;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||
private View container;
|
||||
private TextView displayName;
|
||||
private TextView username;
|
||||
private TextView sinceCreated;
|
||||
private TextView content;
|
||||
private ImageView avatar;
|
||||
private View rebloggedBar;
|
||||
private TextView rebloggedByDisplayName;
|
||||
private ImageButton replyButton;
|
||||
private SparkButton reblogButton;
|
||||
private SparkButton favouriteButton;
|
||||
private ImageButton moreButton;
|
||||
private boolean favourited;
|
||||
private boolean reblogged;
|
||||
private ImageView mediaPreview0;
|
||||
private ImageView mediaPreview1;
|
||||
private ImageView mediaPreview2;
|
||||
private ImageView mediaPreview3;
|
||||
private View sensitiveMediaWarning;
|
||||
private View contentWarningBar;
|
||||
private TextView contentWarningDescription;
|
||||
private ToggleButton contentWarningButton;
|
||||
|
||||
StatusViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
container = itemView.findViewById(R.id.status_container);
|
||||
displayName = (TextView) itemView.findViewById(R.id.status_display_name);
|
||||
username = (TextView) itemView.findViewById(R.id.status_username);
|
||||
sinceCreated = (TextView) itemView.findViewById(R.id.status_since_created);
|
||||
content = (TextView) itemView.findViewById(R.id.status_content);
|
||||
avatar = (ImageView) itemView.findViewById(R.id.status_avatar);
|
||||
rebloggedBar = itemView.findViewById(R.id.status_reblogged_bar);
|
||||
rebloggedByDisplayName = (TextView) itemView.findViewById(R.id.status_reblogged);
|
||||
replyButton = (ImageButton) itemView.findViewById(R.id.status_reply);
|
||||
reblogButton = (SparkButton) itemView.findViewById(R.id.status_reblog);
|
||||
favouriteButton = (SparkButton) itemView.findViewById(R.id.status_favourite);
|
||||
moreButton = (ImageButton) itemView.findViewById(R.id.status_more);
|
||||
reblogged = false;
|
||||
favourited = false;
|
||||
mediaPreview0 = (ImageView) itemView.findViewById(R.id.status_media_preview_0);
|
||||
mediaPreview1 = (ImageView) itemView.findViewById(R.id.status_media_preview_1);
|
||||
mediaPreview2 = (ImageView) itemView.findViewById(R.id.status_media_preview_2);
|
||||
mediaPreview3 = (ImageView) itemView.findViewById(R.id.status_media_preview_3);
|
||||
sensitiveMediaWarning = itemView.findViewById(R.id.status_sensitive_media_warning);
|
||||
contentWarningBar = itemView.findViewById(R.id.status_content_warning_bar);
|
||||
contentWarningDescription =
|
||||
(TextView) itemView.findViewById(R.id.status_content_warning_description);
|
||||
contentWarningButton =
|
||||
(ToggleButton) itemView.findViewById(R.id.status_content_warning_button);
|
||||
}
|
||||
|
||||
private void setDisplayName(String name) {
|
||||
displayName.setText(name);
|
||||
}
|
||||
|
||||
private void setUsername(String name) {
|
||||
Context context = username.getContext();
|
||||
String format = context.getString(R.string.status_username_format);
|
||||
String usernameText = String.format(format, name);
|
||||
username.setText(usernameText);
|
||||
}
|
||||
|
||||
private void setContent(Spanned content, Status.Mention[] mentions,
|
||||
StatusActionListener listener) {
|
||||
/* Redirect URLSpan's in the status content to the listener for viewing tag pages and
|
||||
* account pages. */
|
||||
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) {
|
||||
if (url.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Context context = avatar.getContext();
|
||||
Picasso.with(context)
|
||||
.load(url)
|
||||
.placeholder(R.drawable.avatar_default)
|
||||
.error(R.drawable.avatar_error)
|
||||
.transform(new RoundedTransformation(7, 0))
|
||||
.into(avatar);
|
||||
}
|
||||
|
||||
private void setCreatedAt(@Nullable Date createdAt) {
|
||||
// This is the visible timestamp.
|
||||
String readout;
|
||||
/* This one is for screen-readers. Frequently, they would mispronounce timestamps like "17m"
|
||||
* as 17 meters instead of minutes. */
|
||||
CharSequence readoutAloud;
|
||||
if (createdAt != null) {
|
||||
long then = createdAt.getTime();
|
||||
long now = new Date().getTime();
|
||||
readout = DateUtils.getRelativeTimeSpanString(then, now);
|
||||
readoutAloud = android.text.format.DateUtils.getRelativeTimeSpanString(then, now,
|
||||
android.text.format.DateUtils.SECOND_IN_MILLIS,
|
||||
android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE);
|
||||
} else {
|
||||
// unknown minutes~
|
||||
readout = "?m";
|
||||
readoutAloud = "? minutes";
|
||||
}
|
||||
sinceCreated.setText(readout);
|
||||
sinceCreated.setContentDescription(readoutAloud);
|
||||
}
|
||||
|
||||
private void setRebloggedByDisplayName(String name) {
|
||||
Context context = rebloggedByDisplayName.getContext();
|
||||
String format = context.getString(R.string.status_boosted_format);
|
||||
String boostedText = String.format(format, name);
|
||||
rebloggedByDisplayName.setText(boostedText);
|
||||
rebloggedBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void hideRebloggedByDisplayName() {
|
||||
rebloggedBar.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void setReblogged(boolean reblogged) {
|
||||
this.reblogged = reblogged;
|
||||
reblogButton.setChecked(reblogged);
|
||||
}
|
||||
|
||||
/** This should only be called after setReblogged, in order to override the tint correctly. */
|
||||
private void setRebloggingEnabled(boolean enabled) {
|
||||
reblogButton.setEnabled(enabled);
|
||||
|
||||
if (enabled) {
|
||||
int inactiveId = ThemeUtils.getDrawableId(reblogButton.getContext(),
|
||||
R.attr.status_reblog_inactive_drawable, R.drawable.reblog_inactive_dark);
|
||||
reblogButton.setInactiveImage(inactiveId);
|
||||
reblogButton.setActiveImage(R.drawable.reblog_active);
|
||||
} else {
|
||||
int disabledId = ThemeUtils.getDrawableId(reblogButton.getContext(),
|
||||
R.attr.status_reblog_disabled_drawable, R.drawable.reblog_disabled_dark);
|
||||
reblogButton.setInactiveImage(disabledId);
|
||||
reblogButton.setActiveImage(disabledId);
|
||||
}
|
||||
}
|
||||
|
||||
private void setFavourited(boolean favourited) {
|
||||
this.favourited = favourited;
|
||||
favouriteButton.setChecked(favourited);
|
||||
}
|
||||
|
||||
private void setMediaPreviews(final Status.MediaAttachment[] attachments,
|
||||
boolean sensitive, final StatusActionListener listener) {
|
||||
final ImageView[] previews = {
|
||||
mediaPreview0,
|
||||
mediaPreview1,
|
||||
mediaPreview2,
|
||||
mediaPreview3
|
||||
};
|
||||
Context context = mediaPreview0.getContext();
|
||||
|
||||
int mediaPreviewUnloadedId = ThemeUtils.getDrawableId(itemView.getContext(),
|
||||
R.attr.media_preview_unloaded_drawable, android.R.color.black);
|
||||
|
||||
final int n = Math.min(attachments.length, Status.MAX_MEDIA_ATTACHMENTS);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
String previewUrl = attachments[i].previewUrl;
|
||||
|
||||
previews[i].setVisibility(View.VISIBLE);
|
||||
|
||||
if(previewUrl == null || previewUrl.isEmpty()) {
|
||||
Picasso.with(context)
|
||||
.load(mediaPreviewUnloadedId)
|
||||
.into(previews[i]);
|
||||
} else {
|
||||
Picasso.with(context)
|
||||
.load(previewUrl)
|
||||
.placeholder(mediaPreviewUnloadedId)
|
||||
.into(previews[i]);
|
||||
}
|
||||
|
||||
final String url = attachments[i].url;
|
||||
final Status.MediaAttachment.Type type = attachments[i].type;
|
||||
|
||||
if(url == null || url.isEmpty()) {
|
||||
previews[i].setOnClickListener(null);
|
||||
} else {
|
||||
previews[i].setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onViewMedia(url, type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (sensitive) {
|
||||
sensitiveMediaWarning.setVisibility(View.VISIBLE);
|
||||
sensitiveMediaWarning.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
v.setVisibility(View.GONE);
|
||||
v.setOnClickListener(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Hide any of the placeholder previews beyond the ones set.
|
||||
for (int i = n; i < Status.MAX_MEDIA_ATTACHMENTS; i++) {
|
||||
previews[i].setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideSensitiveMediaWarning() {
|
||||
sensitiveMediaWarning.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void setSpoilerText(String spoilerText) {
|
||||
contentWarningDescription.setText(spoilerText);
|
||||
contentWarningBar.setVisibility(View.VISIBLE);
|
||||
content.setVisibility(View.GONE);
|
||||
contentWarningButton.setChecked(false);
|
||||
contentWarningButton.setOnCheckedChangeListener(
|
||||
new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
content.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
content.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void hideSpoilerText() {
|
||||
contentWarningBar.setVisibility(View.GONE);
|
||||
content.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void setupButtons(final StatusActionListener listener, final String accountId) {
|
||||
/* Originally position was passed through to all these listeners, but it caused several
|
||||
* bugs where other statuses in the list would be removed or added and cause the position
|
||||
* here to become outdated. So, getting the adapter position at the time the listener is
|
||||
* actually called is the appropriate solution. */
|
||||
avatar.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onViewAccount(accountId);
|
||||
}
|
||||
});
|
||||
replyButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onReply(position);
|
||||
}
|
||||
}
|
||||
});
|
||||
reblogButton.setEventListener(new SparkEventListener() {
|
||||
@Override
|
||||
public void onEvent(ImageView button, boolean buttonState) {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onReblog(!reblogged, position);
|
||||
}
|
||||
}
|
||||
});
|
||||
favouriteButton.setEventListener(new SparkEventListener() {
|
||||
@Override
|
||||
public void onEvent(ImageView button, boolean buttonState) {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onFavourite(!favourited, position);
|
||||
}
|
||||
}
|
||||
});
|
||||
moreButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onMore(v, position);
|
||||
}
|
||||
}
|
||||
});
|
||||
/* Even though the content TextView is a child of the container, it won't respond to clicks
|
||||
* if it contains URLSpans without also setting its listener. The surrounding spans will
|
||||
* just eat the clicks instead of deferring to the parent listener, but WILL respond to a
|
||||
* listener directly on the TextView, for whatever reason. */
|
||||
View.OnClickListener viewThreadListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onViewThread(position);
|
||||
}
|
||||
}
|
||||
};
|
||||
content.setOnClickListener(viewThreadListener);
|
||||
container.setOnClickListener(viewThreadListener);
|
||||
}
|
||||
|
||||
void setupWithStatus(Status status, StatusActionListener listener) {
|
||||
Status realStatus = status.getActionableStatus();
|
||||
|
||||
setDisplayName(realStatus.account.getDisplayName());
|
||||
setUsername(realStatus.account.username);
|
||||
setCreatedAt(realStatus.createdAt);
|
||||
setContent(realStatus.content, realStatus.mentions, listener);
|
||||
setAvatar(realStatus.account.avatar);
|
||||
setReblogged(realStatus.reblogged);
|
||||
setFavourited(realStatus.favourited);
|
||||
String rebloggedByDisplayName = status.account.getDisplayName();
|
||||
if (status.reblog == null) {
|
||||
hideRebloggedByDisplayName();
|
||||
} else {
|
||||
setRebloggedByDisplayName(rebloggedByDisplayName);
|
||||
}
|
||||
Status.MediaAttachment[] attachments = realStatus.attachments;
|
||||
boolean sensitive = realStatus.sensitive;
|
||||
setMediaPreviews(attachments, sensitive, listener);
|
||||
/* A status without attachments is sometimes still marked sensitive, so it's necessary to
|
||||
* check both whether there are any attachments and if it's marked sensitive. */
|
||||
if (!sensitive || attachments.length == 0) {
|
||||
hideSensitiveMediaWarning();
|
||||
}
|
||||
setupButtons(listener, realStatus.account.id);
|
||||
setRebloggingEnabled(status.rebloggingAllowed());
|
||||
if (realStatus.spoilerText.isEmpty()) {
|
||||
hideSpoilerText();
|
||||
} else {
|
||||
setSpoilerText(realStatus.spoilerText);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/* 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.adapter;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
|
||||
private List<Status> statuses;
|
||||
private StatusActionListener statusActionListener;
|
||||
private int statusIndex;
|
||||
|
||||
public ThreadAdapter(StatusActionListener listener) {
|
||||
this.statusActionListener = listener;
|
||||
this.statuses = new ArrayList<>();
|
||||
this.statusIndex = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_status, parent, false);
|
||||
return new StatusViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||
StatusViewHolder holder = (StatusViewHolder) viewHolder;
|
||||
Status status = statuses.get(position);
|
||||
holder.setupWithStatus(status, statusActionListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return statuses.size();
|
||||
}
|
||||
|
||||
public Status getItem(int position) {
|
||||
return statuses.get(position);
|
||||
}
|
||||
|
||||
public void removeItem(int position) {
|
||||
statuses.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
}
|
||||
|
||||
public void removeAllByAccountId(String accountId) {
|
||||
for (int i = 0; i < statuses.size();) {
|
||||
Status status = statuses.get(i);
|
||||
if (accountId.equals(status.account.id)) {
|
||||
statuses.remove(i);
|
||||
notifyItemRemoved(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int setStatus(Status status) {
|
||||
if (statuses.size() > 0 && statuses.get(statusIndex).equals(status)) {
|
||||
// Do not add this status on refresh, it's already in there.
|
||||
statuses.set(statusIndex, status);
|
||||
return statusIndex;
|
||||
}
|
||||
int i = statusIndex;
|
||||
statuses.add(i, status);
|
||||
notifyItemInserted(i);
|
||||
return i;
|
||||
}
|
||||
|
||||
public void setContext(List<Status> ancestors, List<Status> descendants) {
|
||||
Status mainStatus = null;
|
||||
|
||||
// In case of refresh, remove old ancestors and descendants first. We'll remove all blindly,
|
||||
// as we have no guarantee on their order to be the same as before
|
||||
int oldSize = statuses.size();
|
||||
if (oldSize > 0) {
|
||||
mainStatus = statuses.get(statusIndex);
|
||||
statuses.clear();
|
||||
notifyItemRangeRemoved(0, oldSize);
|
||||
}
|
||||
|
||||
// Insert newly fetched ancestors
|
||||
statusIndex = ancestors.size();
|
||||
statuses.addAll(0, ancestors);
|
||||
notifyItemRangeInserted(0, statusIndex);
|
||||
|
||||
if (mainStatus != null) {
|
||||
// In case we needed to delete everything (which is way easier than deleting
|
||||
// everything except one), re-insert the remaining status here.
|
||||
statuses.add(statusIndex, mainStatus);
|
||||
notifyItemInserted(statusIndex);
|
||||
}
|
||||
|
||||
// Insert newly fetched descendants
|
||||
int end = statuses.size();
|
||||
statuses.addAll(descendants);
|
||||
notifyItemRangeInserted(end, descendants.size());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
/* 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.adapter;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
|
||||
private static final int VIEW_TYPE_STATUS = 0;
|
||||
private static final int VIEW_TYPE_FOOTER = 1;
|
||||
|
||||
public enum FooterState {
|
||||
EMPTY,
|
||||
END,
|
||||
LOADING
|
||||
}
|
||||
|
||||
private List<Status> statuses;
|
||||
private StatusActionListener statusListener;
|
||||
private FooterState footerState = FooterState.END;
|
||||
|
||||
public TimelineAdapter(StatusActionListener statusListener) {
|
||||
super();
|
||||
statuses = new ArrayList<>();
|
||||
this.statusListener = statusListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
|
||||
switch (viewType) {
|
||||
default:
|
||||
case VIEW_TYPE_STATUS: {
|
||||
View view = LayoutInflater.from(viewGroup.getContext())
|
||||
.inflate(R.layout.item_status, viewGroup, false);
|
||||
return new StatusViewHolder(view);
|
||||
}
|
||||
case VIEW_TYPE_FOOTER: {
|
||||
View view;
|
||||
switch (footerState) {
|
||||
default:
|
||||
case LOADING:
|
||||
view = LayoutInflater.from(viewGroup.getContext())
|
||||
.inflate(R.layout.item_footer, viewGroup, false);
|
||||
break;
|
||||
case END: {
|
||||
view = LayoutInflater.from(viewGroup.getContext())
|
||||
.inflate(R.layout.item_footer_end, viewGroup, false);
|
||||
break;
|
||||
}
|
||||
case EMPTY: {
|
||||
view = LayoutInflater.from(viewGroup.getContext())
|
||||
.inflate(R.layout.item_footer_empty, viewGroup, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new FooterViewHolder(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setFooterState(FooterState newFooterState) {
|
||||
FooterState oldValue = footerState;
|
||||
footerState = newFooterState;
|
||||
if (footerState != oldValue) {
|
||||
notifyItemChanged(statuses.size());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||
if (position < statuses.size()) {
|
||||
StatusViewHolder holder = (StatusViewHolder) viewHolder;
|
||||
Status status = statuses.get(position);
|
||||
holder.setupWithStatus(status, statusListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return statuses.size() + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == statuses.size()) {
|
||||
return VIEW_TYPE_FOOTER;
|
||||
} else {
|
||||
return VIEW_TYPE_STATUS;
|
||||
}
|
||||
}
|
||||
|
||||
public void update(List<Status> newStatuses) {
|
||||
if (newStatuses == null || newStatuses.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (statuses.isEmpty()) {
|
||||
statuses = newStatuses;
|
||||
} else {
|
||||
int index = statuses.indexOf(newStatuses.get(newStatuses.size() - 1));
|
||||
for (int i = 0; i < index; i++) {
|
||||
statuses.remove(0);
|
||||
}
|
||||
int newIndex = newStatuses.indexOf(statuses.get(0));
|
||||
if (newIndex == -1) {
|
||||
statuses.addAll(0, newStatuses);
|
||||
} else {
|
||||
statuses.addAll(0, newStatuses.subList(0, newIndex));
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void addItems(List<Status> newStatuses) {
|
||||
int end = statuses.size();
|
||||
statuses.addAll(newStatuses);
|
||||
notifyItemRangeInserted(end, newStatuses.size());
|
||||
}
|
||||
|
||||
public void removeItem(int position) {
|
||||
statuses.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
}
|
||||
|
||||
public void removeAllByAccountId(String accountId) {
|
||||
for (int i = 0; i < statuses.size();) {
|
||||
Status status = statuses.get(i);
|
||||
if (accountId.equals(status.account.id)) {
|
||||
statuses.remove(i);
|
||||
notifyItemRemoved(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Status getItem(int position) {
|
||||
if (position >= 0 && position < statuses.size()) {
|
||||
return statuses.get(position);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue