Adds a toot thread viewing mode. Also, many files were missing and didn't push so the previous commits may have been very wrong?
This commit is contained in:
parent
32fecabd7f
commit
b00a3cf443
31 changed files with 1274 additions and 566 deletions
|
@ -0,0 +1,5 @@
|
|||
package com.keylesspalace.tusky;
|
||||
|
||||
public interface AdapterItemRemover {
|
||||
void removeItem(int position);
|
||||
}
|
|
@ -323,7 +323,7 @@ public class ComposeActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
private void onSendSuccess() {
|
||||
Toast.makeText(this, "Toot!", Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(this, getString(R.string.confirmation_send), Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
|
|
50
app/src/main/java/com/keylesspalace/tusky/DateUtils.java
Normal file
50
app/src/main/java/com/keylesspalace/tusky/DateUtils.java
Normal file
|
@ -0,0 +1,50 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is part of Tusky.
|
||||
*
|
||||
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||
* <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is part of Tusky.
|
||||
*
|
||||
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||
* <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
public class FooterViewHolder extends RecyclerView.ViewHolder {
|
||||
private LinearLayout retryBar;
|
||||
private Button retry;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
public FooterViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
retryBar = (LinearLayout) itemView.findViewById(R.id.footer_retry_bar);
|
||||
retry = (Button) itemView.findViewById(R.id.footer_retry_button);
|
||||
progressBar = (ProgressBar) itemView.findViewById(R.id.footer_progress_bar);
|
||||
progressBar.setIndeterminate(true);
|
||||
}
|
||||
|
||||
public void setupButton(final FooterActionListener listener) {
|
||||
retry.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onLoadMore();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void showRetry(boolean show) {
|
||||
if (!show) {
|
||||
retryBar.setVisibility(View.GONE);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
retryBar.setVisibility(View.VISIBLE);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,4 +55,10 @@ public class Notification {
|
|||
public void setStatus(Status status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public boolean hasStatusType() {
|
||||
return type == Type.MENTION
|
||||
|| type == Type.FAVOURITE
|
||||
|| type == Type.REBLOG;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,39 +16,128 @@
|
|||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
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.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||
private List<Notification> notifications = new ArrayList<>();
|
||||
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;
|
||||
|
||||
private List<Notification> notifications;
|
||||
private StatusActionListener statusListener;
|
||||
private FooterActionListener footerListener;
|
||||
|
||||
public NotificationsAdapter(StatusActionListener statusListener,
|
||||
FooterActionListener footerListener) {
|
||||
super();
|
||||
notifications = new ArrayList<>();
|
||||
this.statusListener = statusListener;
|
||||
this.footerListener = footerListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_notification, parent, false);
|
||||
return new ViewHolder(view);
|
||||
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 = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_footer, parent, false);
|
||||
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) {
|
||||
ViewHolder holder = (ViewHolder) viewHolder;
|
||||
Notification notification = notifications.get(position);
|
||||
holder.setMessage(notification.getType(), notification.getDisplayName());
|
||||
if (position < notifications.size()) {
|
||||
Notification notification = notifications.get(position);
|
||||
Notification.Type type = notification.getType();
|
||||
switch (type) {
|
||||
case MENTION: {
|
||||
StatusViewHolder holder = (StatusViewHolder) viewHolder;
|
||||
Status status = notification.getStatus();
|
||||
assert(status != null);
|
||||
holder.setupWithStatus(status, statusListener, position);
|
||||
break;
|
||||
}
|
||||
case FAVOURITE:
|
||||
case REBLOG: {
|
||||
StatusNotificationViewHolder holder = (StatusNotificationViewHolder) viewHolder;
|
||||
holder.setMessage(type, notification.getDisplayName(),
|
||||
notification.getStatus());
|
||||
break;
|
||||
}
|
||||
case FOLLOW: {
|
||||
FollowViewHolder holder = (FollowViewHolder) viewHolder;
|
||||
holder.setMessage(notification.getDisplayName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FooterViewHolder holder = (FooterViewHolder) viewHolder;
|
||||
holder.setupButton(footerListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return notifications.size();
|
||||
return notifications.size() + 1;
|
||||
}
|
||||
|
||||
public Notification getItem(int position) {
|
||||
return notifications.get(position);
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == notifications.size()) {
|
||||
return VIEW_TYPE_FOOTER;
|
||||
} else {
|
||||
Notification notification = notifications.get(position);
|
||||
switch (notification.getType()) {
|
||||
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 int update(List<Notification> new_notifications) {
|
||||
|
@ -76,39 +165,62 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
|||
notifyItemRangeInserted(end, new_notifications.size());
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public void removeItem(int position) {
|
||||
notifications.remove(position);
|
||||
notifyItemChanged(position);
|
||||
}
|
||||
|
||||
public static class FollowViewHolder extends RecyclerView.ViewHolder {
|
||||
private TextView message;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
public FollowViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
message = (TextView) itemView.findViewById(R.id.notification_text);
|
||||
}
|
||||
|
||||
public void setMessage(Notification.Type type, String displayName) {
|
||||
public void setMessage(String displayName) {
|
||||
Context context = message.getContext();
|
||||
String wholeMessage = "";
|
||||
switch (type) {
|
||||
case MENTION: {
|
||||
wholeMessage = displayName + " mentioned you";
|
||||
break;
|
||||
}
|
||||
case REBLOG: {
|
||||
String format = context.getString(R.string.notification_reblog_format);
|
||||
wholeMessage = String.format(format, displayName);
|
||||
break;
|
||||
}
|
||||
case FAVOURITE: {
|
||||
String format = context.getString(R.string.notification_favourite_format);
|
||||
wholeMessage = String.format(format, displayName);
|
||||
break;
|
||||
}
|
||||
case FOLLOW: {
|
||||
String format = context.getString(R.string.notification_follow_format);
|
||||
wholeMessage = String.format(format, displayName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
String format = context.getString(R.string.notification_follow_format);
|
||||
String wholeMessage = String.format(format, displayName);
|
||||
message.setText(wholeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public static class StatusNotificationViewHolder extends RecyclerView.ViewHolder {
|
||||
private TextView message;
|
||||
private ImageView icon;
|
||||
private TextView statusContent;
|
||||
|
||||
public 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);
|
||||
}
|
||||
|
||||
public 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_favourited);
|
||||
format = context.getString(R.string.notification_favourite_format);
|
||||
break;
|
||||
}
|
||||
case REBLOG: {
|
||||
icon.setImageResource(R.drawable.ic_reblogged);
|
||||
format = context.getString(R.string.notification_reblog_format);
|
||||
break;
|
||||
}
|
||||
}
|
||||
String wholeMessage = String.format(format, displayName);
|
||||
message.setText(wholeMessage);
|
||||
String timestamp = DateUtils.getRelativeTimeSpanString(
|
||||
status.getCreatedAt().getTime(),
|
||||
new Date().getTime());
|
||||
statusContent.setText(String.format("%s: ", timestamp));
|
||||
statusContent.append(status.getContent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -172,12 +172,10 @@ public class NotificationsFragment extends SFragment implements
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
sendFetchNotificationsRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadMore() {
|
||||
Notification notification = adapter.getItem(adapter.getItemCount() - 2);
|
||||
if (notification != null) {
|
||||
|
@ -187,32 +185,32 @@ public class NotificationsFragment extends SFragment implements
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReply(int position) {
|
||||
Notification notification = adapter.getItem(position);
|
||||
super.reply(notification.getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReblog(boolean reblog, int position) {
|
||||
Notification notification = adapter.getItem(position);
|
||||
super.reblog(notification.getStatus(), reblog, adapter, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFavourite(boolean favourite, int position) {
|
||||
Notification notification = adapter.getItem(position);
|
||||
super.favourite(notification.getStatus(), favourite, adapter, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMore(View view, int position) {
|
||||
Notification notification = adapter.getItem(position);
|
||||
super.more(notification.getStatus(), view, adapter, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewMedia(String url, Status.MediaAttachment.Type type) {
|
||||
super.viewMedia(url, type);
|
||||
}
|
||||
|
||||
public void onViewThread(int position) {
|
||||
Notification notification = adapter.getItem(position);
|
||||
super.viewThread(notification.getStatus());
|
||||
}
|
||||
}
|
||||
|
|
247
app/src/main/java/com/keylesspalace/tusky/SFragment.java
Normal file
247
app/src/main/java/com/keylesspalace/tusky/SFragment.java
Normal file
|
@ -0,0 +1,247 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is part of Tusky.
|
||||
*
|
||||
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||
* <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v7.widget.PopupMenu;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.volley.AuthFailureError;
|
||||
import com.android.volley.Request;
|
||||
import com.android.volley.Response;
|
||||
import com.android.volley.VolleyError;
|
||||
import com.android.volley.toolbox.JsonObjectRequest;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/* Note from Andrew on Jan. 22, 2017: This class is a design problem for me, so I left it with an
|
||||
* awkward name. TimelineFragment and NotificationFragment have significant overlap but the nature
|
||||
* of that is complicated by how they're coupled with Status and Notification and the corresponding
|
||||
* adapters. I feel like the profile pages and thread viewer, which I haven't made yet, will also
|
||||
* overlap functionality. So, I'm momentarily leaving it and hopefully working on those will clear
|
||||
* up what needs to be where. */
|
||||
public class SFragment extends Fragment {
|
||||
protected String domain;
|
||||
protected String accessToken;
|
||||
protected String loggedInAccountId;
|
||||
protected String loggedInUsername;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
SharedPreferences preferences = getContext().getSharedPreferences(
|
||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||
domain = preferences.getString("domain", null);
|
||||
accessToken = preferences.getString("accessToken", null);
|
||||
assert(domain != null);
|
||||
assert(accessToken != null);
|
||||
|
||||
sendUserInfoRequest();
|
||||
}
|
||||
|
||||
protected void sendRequest(
|
||||
int method, String endpoint, JSONObject parameters,
|
||||
@Nullable Response.Listener<JSONObject> responseListener) {
|
||||
if (responseListener == null) {
|
||||
// Use a dummy listener if one wasn't specified so the request can be constructed.
|
||||
responseListener = new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {}
|
||||
};
|
||||
}
|
||||
String url = "https://" + domain + endpoint;
|
||||
JsonObjectRequest request = new JsonObjectRequest(
|
||||
method, url, parameters, responseListener,
|
||||
new Response.ErrorListener() {
|
||||
@Override
|
||||
public void onErrorResponse(VolleyError error) {
|
||||
System.err.println(error.getMessage());
|
||||
}
|
||||
}) {
|
||||
@Override
|
||||
public Map<String, String> getHeaders() throws AuthFailureError {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Authorization", "Bearer " + accessToken);
|
||||
return headers;
|
||||
}
|
||||
};
|
||||
VolleySingleton.getInstance(getContext()).addToRequestQueue(request);
|
||||
}
|
||||
|
||||
protected void postRequest(String endpoint) {
|
||||
sendRequest(Request.Method.POST, endpoint, null, null);
|
||||
}
|
||||
|
||||
private void sendUserInfoRequest() {
|
||||
sendRequest(Request.Method.GET, getString(R.string.endpoint_verify_credentials), null,
|
||||
new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {
|
||||
try {
|
||||
loggedInAccountId = response.getString("id");
|
||||
loggedInUsername = response.getString("acct");
|
||||
} catch (JSONException e) {
|
||||
//TODO: Help
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void reply(Status status) {
|
||||
String inReplyToId = status.getId();
|
||||
Status.Mention[] mentions = status.getMentions();
|
||||
List<String> mentionedUsernames = new ArrayList<>();
|
||||
for (int i = 0; i < mentions.length; i++) {
|
||||
mentionedUsernames.add(mentions[i].getUsername());
|
||||
}
|
||||
mentionedUsernames.add(status.getUsername());
|
||||
mentionedUsernames.remove(loggedInUsername);
|
||||
Intent intent = new Intent(getContext(), ComposeActivity.class);
|
||||
intent.putExtra("in_reply_to_id", inReplyToId);
|
||||
intent.putExtra("mentioned_usernames", mentionedUsernames.toArray(new String[0]));
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
protected void reblog(final Status status, final boolean reblog,
|
||||
final RecyclerView.Adapter adapter, final int position) {
|
||||
String id = status.getId();
|
||||
String endpoint;
|
||||
if (reblog) {
|
||||
endpoint = String.format(getString(R.string.endpoint_reblog), id);
|
||||
} else {
|
||||
endpoint = String.format(getString(R.string.endpoint_unreblog), id);
|
||||
}
|
||||
sendRequest(Request.Method.POST, endpoint, null,
|
||||
new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {
|
||||
status.setReblogged(reblog);
|
||||
adapter.notifyItemChanged(position);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void favourite(final Status status, final boolean favourite,
|
||||
final RecyclerView.Adapter adapter, final int position) {
|
||||
String id = status.getId();
|
||||
String endpoint;
|
||||
if (favourite) {
|
||||
endpoint = String.format(getString(R.string.endpoint_favourite), id);
|
||||
} else {
|
||||
endpoint = String.format(getString(R.string.endpoint_unfavourite), id);
|
||||
}
|
||||
sendRequest(Request.Method.POST, endpoint, null, new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {
|
||||
status.setFavourited(favourite);
|
||||
adapter.notifyItemChanged(position);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void follow(String id) {
|
||||
String endpoint = String.format(getString(R.string.endpoint_follow), id);
|
||||
postRequest(endpoint);
|
||||
}
|
||||
|
||||
private void block(String id) {
|
||||
String endpoint = String.format(getString(R.string.endpoint_block), id);
|
||||
postRequest(endpoint);
|
||||
}
|
||||
|
||||
private void delete(String id) {
|
||||
String endpoint = String.format(getString(R.string.endpoint_delete), id);
|
||||
sendRequest(Request.Method.DELETE, endpoint, null, null);
|
||||
}
|
||||
|
||||
protected void more(Status status, View view, final AdapterItemRemover adapter,
|
||||
final int position) {
|
||||
final String id = status.getId();
|
||||
final String accountId = status.getAccountId();
|
||||
PopupMenu popup = new PopupMenu(getContext(), view);
|
||||
// Give a different menu depending on whether this is the user's own toot or not.
|
||||
if (loggedInAccountId == null || !loggedInAccountId.equals(accountId)) {
|
||||
popup.inflate(R.menu.status_more);
|
||||
} else {
|
||||
popup.inflate(R.menu.status_more_for_user);
|
||||
}
|
||||
popup.setOnMenuItemClickListener(
|
||||
new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.status_follow: {
|
||||
follow(accountId);
|
||||
return true;
|
||||
}
|
||||
case R.id.status_block: {
|
||||
block(accountId);
|
||||
return true;
|
||||
}
|
||||
case R.id.status_delete: {
|
||||
delete(id);
|
||||
adapter.removeItem(position);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
popup.show();
|
||||
}
|
||||
|
||||
protected void viewMedia(String url, Status.MediaAttachment.Type type) {
|
||||
switch (type) {
|
||||
case IMAGE: {
|
||||
Fragment newFragment = ViewMediaFragment.newInstance(url);
|
||||
FragmentManager manager = getFragmentManager();
|
||||
manager.beginTransaction()
|
||||
.add(R.id.overlay_fragment_container, newFragment)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
break;
|
||||
}
|
||||
case VIDEO: {
|
||||
Intent intent = new Intent(getContext(), ViewVideoActivity.class);
|
||||
intent.putExtra("url", url);
|
||||
startActivity(intent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void viewThread(Status status) {
|
||||
Intent intent = new Intent(getContext(), ViewThreadActivity.class);
|
||||
intent.putExtra("id", status.getId());
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
|
@ -23,4 +23,5 @@ public interface StatusActionListener {
|
|||
void onFavourite(final boolean favourite, final int position);
|
||||
void onMore(View view, final int position);
|
||||
void onViewMedia(String url, Status.MediaAttachment.Type type);
|
||||
void onViewThread(int position);
|
||||
}
|
||||
|
|
266
app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java
Normal file
266
app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java
Normal file
|
@ -0,0 +1,266 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is part of Tusky.
|
||||
*
|
||||
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||
* <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Spanned;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.volley.toolbox.ImageLoader;
|
||||
import com.android.volley.toolbox.NetworkImageView;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||
private View container;
|
||||
private TextView displayName;
|
||||
private TextView username;
|
||||
private TextView sinceCreated;
|
||||
private TextView content;
|
||||
private NetworkImageView avatar;
|
||||
private ImageView boostedIcon;
|
||||
private TextView boostedByUsername;
|
||||
private ImageButton replyButton;
|
||||
private ImageButton reblogButton;
|
||||
private ImageButton favouriteButton;
|
||||
private ImageButton moreButton;
|
||||
private boolean favourited;
|
||||
private boolean reblogged;
|
||||
private NetworkImageView mediaPreview0;
|
||||
private NetworkImageView mediaPreview1;
|
||||
private NetworkImageView mediaPreview2;
|
||||
private NetworkImageView mediaPreview3;
|
||||
private View sensitiveMediaWarning;
|
||||
|
||||
public 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 = (NetworkImageView) itemView.findViewById(R.id.status_avatar);
|
||||
boostedIcon = (ImageView) itemView.findViewById(R.id.status_boosted_icon);
|
||||
boostedByUsername = (TextView) itemView.findViewById(R.id.status_boosted);
|
||||
replyButton = (ImageButton) itemView.findViewById(R.id.status_reply);
|
||||
reblogButton = (ImageButton) itemView.findViewById(R.id.status_reblog);
|
||||
favouriteButton = (ImageButton) itemView.findViewById(R.id.status_favourite);
|
||||
moreButton = (ImageButton) itemView.findViewById(R.id.status_more);
|
||||
reblogged = false;
|
||||
favourited = false;
|
||||
mediaPreview0 = (NetworkImageView) itemView.findViewById(R.id.status_media_preview_0);
|
||||
mediaPreview1 = (NetworkImageView) itemView.findViewById(R.id.status_media_preview_1);
|
||||
mediaPreview2 = (NetworkImageView) itemView.findViewById(R.id.status_media_preview_2);
|
||||
mediaPreview3 = (NetworkImageView) itemView.findViewById(R.id.status_media_preview_3);
|
||||
mediaPreview0.setDefaultImageResId(R.drawable.media_preview_unloaded);
|
||||
mediaPreview1.setDefaultImageResId(R.drawable.media_preview_unloaded);
|
||||
mediaPreview2.setDefaultImageResId(R.drawable.media_preview_unloaded);
|
||||
mediaPreview3.setDefaultImageResId(R.drawable.media_preview_unloaded);
|
||||
sensitiveMediaWarning = itemView.findViewById(R.id.status_sensitive_media_warning);
|
||||
}
|
||||
|
||||
public void setDisplayName(String name) {
|
||||
displayName.setText(name);
|
||||
}
|
||||
|
||||
public 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);
|
||||
}
|
||||
|
||||
public void setContent(Spanned content) {
|
||||
this.content.setText(content);
|
||||
}
|
||||
|
||||
public void setAvatar(String url) {
|
||||
Context context = avatar.getContext();
|
||||
ImageLoader imageLoader = VolleySingleton.getInstance(context).getImageLoader();
|
||||
avatar.setImageUrl(url, imageLoader);
|
||||
avatar.setDefaultImageResId(R.drawable.avatar_default);
|
||||
avatar.setErrorImageResId(R.drawable.avatar_error);
|
||||
}
|
||||
|
||||
public void setCreatedAt(@Nullable Date createdAt) {
|
||||
String readout;
|
||||
if (createdAt != null) {
|
||||
long then = createdAt.getTime();
|
||||
long now = new Date().getTime();
|
||||
readout = DateUtils.getRelativeTimeSpanString(then, now);
|
||||
} else {
|
||||
readout = "?m"; // unknown minutes~
|
||||
}
|
||||
sinceCreated.setText(readout);
|
||||
}
|
||||
|
||||
public void setRebloggedByUsername(String name) {
|
||||
Context context = boostedByUsername.getContext();
|
||||
String format = context.getString(R.string.status_boosted_format);
|
||||
String boostedText = String.format(format, name);
|
||||
boostedByUsername.setText(boostedText);
|
||||
boostedIcon.setVisibility(View.VISIBLE);
|
||||
boostedByUsername.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void hideRebloggedByUsername() {
|
||||
boostedIcon.setVisibility(View.GONE);
|
||||
boostedByUsername.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void setReblogged(boolean reblogged) {
|
||||
this.reblogged = reblogged;
|
||||
if (!reblogged) {
|
||||
reblogButton.setImageResource(R.drawable.ic_reblog_off);
|
||||
} else {
|
||||
reblogButton.setImageResource(R.drawable.ic_reblog_on);
|
||||
}
|
||||
}
|
||||
|
||||
public void disableReblogging() {
|
||||
reblogButton.setEnabled(false);
|
||||
reblogButton.setImageResource(R.drawable.ic_reblog_disabled);
|
||||
}
|
||||
|
||||
public void setFavourited(boolean favourited) {
|
||||
this.favourited = favourited;
|
||||
if (!favourited) {
|
||||
favouriteButton.setImageResource(R.drawable.ic_favourite_off);
|
||||
} else {
|
||||
favouriteButton.setImageResource(R.drawable.ic_favourite_on);
|
||||
}
|
||||
}
|
||||
|
||||
public void setMediaPreviews(final Status.MediaAttachment[] attachments,
|
||||
boolean sensitive, final StatusActionListener listener) {
|
||||
final NetworkImageView[] previews = {
|
||||
mediaPreview0,
|
||||
mediaPreview1,
|
||||
mediaPreview2,
|
||||
mediaPreview3
|
||||
};
|
||||
Context context = mediaPreview0.getContext();
|
||||
ImageLoader imageLoader = VolleySingleton.getInstance(context).getImageLoader();
|
||||
final int n = Math.min(attachments.length, Status.MAX_MEDIA_ATTACHMENTS);
|
||||
for (int i = 0; i < n; i++) {
|
||||
String previewUrl = attachments[i].getPreviewUrl();
|
||||
previews[i].setImageUrl(previewUrl, imageLoader);
|
||||
if (!sensitive) {
|
||||
previews[i].setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
previews[i].setVisibility(View.GONE);
|
||||
}
|
||||
final String url = attachments[i].getUrl();
|
||||
final Status.MediaAttachment.Type type = attachments[i].getType();
|
||||
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);
|
||||
for (int i = 0; i < n; i++) {
|
||||
previews[i].setVisibility(View.VISIBLE);
|
||||
}
|
||||
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].setImageUrl(null, imageLoader);
|
||||
previews[i].setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void hideSensitiveMediaWarning() {
|
||||
sensitiveMediaWarning.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void setupButtons(final StatusActionListener listener, final int position) {
|
||||
replyButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onReply(position);
|
||||
}
|
||||
});
|
||||
reblogButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onReblog(!reblogged, position);
|
||||
}
|
||||
});
|
||||
favouriteButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onFavourite(!favourited, position);
|
||||
}
|
||||
});
|
||||
moreButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onMore(v, position);
|
||||
}
|
||||
});
|
||||
container.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onViewThread(position);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setupWithStatus(Status status, StatusActionListener listener, int position) {
|
||||
setDisplayName(status.getDisplayName());
|
||||
setUsername(status.getUsername());
|
||||
setCreatedAt(status.getCreatedAt());
|
||||
setContent(status.getContent());
|
||||
setAvatar(status.getAvatar());
|
||||
setContent(status.getContent());
|
||||
setReblogged(status.getReblogged());
|
||||
setFavourited(status.getFavourited());
|
||||
String rebloggedByUsername = status.getRebloggedByUsername();
|
||||
if (rebloggedByUsername == null) {
|
||||
hideRebloggedByUsername();
|
||||
} else {
|
||||
setRebloggedByUsername(rebloggedByUsername);
|
||||
}
|
||||
Status.MediaAttachment[] attachments = status.getAttachments();
|
||||
boolean sensitive = status.getSensitive();
|
||||
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, position);
|
||||
if (status.getVisibility() == Status.Visibility.PRIVATE) {
|
||||
disableReblogging();
|
||||
}
|
||||
}
|
||||
}
|
83
app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java
Normal file
83
app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java
Normal file
|
@ -0,0 +1,83 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is part of Tusky.
|
||||
*
|
||||
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||
* <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
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, position);
|
||||
}
|
||||
|
||||
@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 int insertStatus(Status status) {
|
||||
int i = statusIndex;
|
||||
statuses.add(i, status);
|
||||
notifyItemInserted(i);
|
||||
return i;
|
||||
}
|
||||
|
||||
public void addAncestors(List<Status> ancestors) {
|
||||
statusIndex = ancestors.size();
|
||||
statuses.addAll(0, ancestors);
|
||||
notifyItemRangeInserted(0, statusIndex);
|
||||
}
|
||||
|
||||
public void addDescendants(List<Status> descendants) {
|
||||
int end = statuses.size();
|
||||
statuses.addAll(descendants);
|
||||
notifyItemRangeInserted(end, descendants.size());
|
||||
}
|
||||
}
|
|
@ -15,30 +15,16 @@
|
|||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.PagerSnapHelper;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.volley.toolbox.ImageLoader;
|
||||
import com.android.volley.toolbox.NetworkImageView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class TimelineAdapter extends RecyclerView.Adapter {
|
||||
public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
|
||||
private static final int VIEW_TYPE_STATUS = 0;
|
||||
private static final int VIEW_TYPE_FOOTER = 1;
|
||||
|
||||
|
@ -76,32 +62,7 @@ public class TimelineAdapter extends RecyclerView.Adapter {
|
|||
if (position < statuses.size()) {
|
||||
StatusViewHolder holder = (StatusViewHolder) viewHolder;
|
||||
Status status = statuses.get(position);
|
||||
holder.setDisplayName(status.getDisplayName());
|
||||
holder.setUsername(status.getUsername());
|
||||
holder.setCreatedAt(status.getCreatedAt());
|
||||
holder.setContent(status.getContent());
|
||||
holder.setAvatar(status.getAvatar());
|
||||
holder.setContent(status.getContent());
|
||||
holder.setReblogged(status.getReblogged());
|
||||
holder.setFavourited(status.getFavourited());
|
||||
String rebloggedByUsername = status.getRebloggedByUsername();
|
||||
if (rebloggedByUsername == null) {
|
||||
holder.hideRebloggedByUsername();
|
||||
} else {
|
||||
holder.setRebloggedByUsername(rebloggedByUsername);
|
||||
}
|
||||
Status.MediaAttachment[] attachments = status.getAttachments();
|
||||
boolean sensitive = status.getSensitive();
|
||||
holder.setMediaPreviews(attachments, sensitive, statusListener);
|
||||
/* 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) {
|
||||
holder.hideSensitiveMediaWarning();
|
||||
}
|
||||
holder.setupButtons(statusListener, position);
|
||||
if (status.getVisibility() == Status.Visibility.PRIVATE) {
|
||||
holder.disableReblogging();
|
||||
}
|
||||
holder.setupWithStatus(status, statusListener, position);
|
||||
} else {
|
||||
FooterViewHolder holder = (FooterViewHolder) viewHolder;
|
||||
holder.setupButton(footerListener);
|
||||
|
@ -158,267 +119,4 @@ public class TimelineAdapter extends RecyclerView.Adapter {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||
private TextView displayName;
|
||||
private TextView username;
|
||||
private TextView sinceCreated;
|
||||
private TextView content;
|
||||
private NetworkImageView avatar;
|
||||
private ImageView boostedIcon;
|
||||
private TextView boostedByUsername;
|
||||
private ImageButton replyButton;
|
||||
private ImageButton reblogButton;
|
||||
private ImageButton favouriteButton;
|
||||
private ImageButton moreButton;
|
||||
private boolean favourited;
|
||||
private boolean reblogged;
|
||||
private NetworkImageView mediaPreview0;
|
||||
private NetworkImageView mediaPreview1;
|
||||
private NetworkImageView mediaPreview2;
|
||||
private NetworkImageView mediaPreview3;
|
||||
private View sensitiveMediaWarning;
|
||||
|
||||
public StatusViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
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 = (NetworkImageView) itemView.findViewById(R.id.status_avatar);
|
||||
boostedIcon = (ImageView) itemView.findViewById(R.id.status_boosted_icon);
|
||||
boostedByUsername = (TextView) itemView.findViewById(R.id.status_boosted);
|
||||
replyButton = (ImageButton) itemView.findViewById(R.id.status_reply);
|
||||
reblogButton = (ImageButton) itemView.findViewById(R.id.status_reblog);
|
||||
favouriteButton = (ImageButton) itemView.findViewById(R.id.status_favourite);
|
||||
moreButton = (ImageButton) itemView.findViewById(R.id.status_more);
|
||||
reblogged = false;
|
||||
favourited = false;
|
||||
mediaPreview0 = (NetworkImageView) itemView.findViewById(R.id.status_media_preview_0);
|
||||
mediaPreview1 = (NetworkImageView) itemView.findViewById(R.id.status_media_preview_1);
|
||||
mediaPreview2 = (NetworkImageView) itemView.findViewById(R.id.status_media_preview_2);
|
||||
mediaPreview3 = (NetworkImageView) itemView.findViewById(R.id.status_media_preview_3);
|
||||
mediaPreview0.setDefaultImageResId(R.drawable.media_preview_unloaded);
|
||||
mediaPreview1.setDefaultImageResId(R.drawable.media_preview_unloaded);
|
||||
mediaPreview2.setDefaultImageResId(R.drawable.media_preview_unloaded);
|
||||
mediaPreview3.setDefaultImageResId(R.drawable.media_preview_unloaded);
|
||||
sensitiveMediaWarning = itemView.findViewById(R.id.status_sensitive_media_warning);
|
||||
}
|
||||
|
||||
public void setDisplayName(String name) {
|
||||
displayName.setText(name);
|
||||
}
|
||||
|
||||
public 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);
|
||||
}
|
||||
|
||||
public void setContent(Spanned content) {
|
||||
this.content.setText(content);
|
||||
}
|
||||
|
||||
public void setAvatar(String url) {
|
||||
Context context = avatar.getContext();
|
||||
ImageLoader imageLoader = VolleySingleton.getInstance(context).getImageLoader();
|
||||
avatar.setImageUrl(url, imageLoader);
|
||||
avatar.setDefaultImageResId(R.drawable.avatar_default);
|
||||
avatar.setErrorImageResId(R.drawable.avatar_error);
|
||||
}
|
||||
|
||||
/* This is a rough duplicate of android.text.format.DateUtils.getRelativeTimeSpanString,
|
||||
* but even with the FORMAT_ABBREV_RELATIVE flag it wasn't abbreviating enough. */
|
||||
private 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;
|
||||
}
|
||||
|
||||
public void setCreatedAt(@Nullable Date createdAt) {
|
||||
String readout;
|
||||
if (createdAt != null) {
|
||||
long then = createdAt.getTime();
|
||||
long now = new Date().getTime();
|
||||
readout = getRelativeTimeSpanString(then, now);
|
||||
} else {
|
||||
readout = "?m"; // unknown minutes~
|
||||
}
|
||||
sinceCreated.setText(readout);
|
||||
}
|
||||
|
||||
public void setRebloggedByUsername(String name) {
|
||||
Context context = boostedByUsername.getContext();
|
||||
String format = context.getString(R.string.status_boosted_format);
|
||||
String boostedText = String.format(format, name);
|
||||
boostedByUsername.setText(boostedText);
|
||||
boostedIcon.setVisibility(View.VISIBLE);
|
||||
boostedByUsername.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void hideRebloggedByUsername() {
|
||||
boostedIcon.setVisibility(View.GONE);
|
||||
boostedByUsername.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void setReblogged(boolean reblogged) {
|
||||
this.reblogged = reblogged;
|
||||
if (!reblogged) {
|
||||
reblogButton.setImageResource(R.drawable.ic_reblog_off);
|
||||
} else {
|
||||
reblogButton.setImageResource(R.drawable.ic_reblog_on);
|
||||
}
|
||||
}
|
||||
|
||||
public void disableReblogging() {
|
||||
reblogButton.setEnabled(false);
|
||||
reblogButton.setImageResource(R.drawable.ic_reblog_disabled);
|
||||
}
|
||||
|
||||
public void setFavourited(boolean favourited) {
|
||||
this.favourited = favourited;
|
||||
if (!favourited) {
|
||||
favouriteButton.setImageResource(R.drawable.ic_favourite_off);
|
||||
} else {
|
||||
favouriteButton.setImageResource(R.drawable.ic_favourite_on);
|
||||
}
|
||||
}
|
||||
|
||||
public void setMediaPreviews(final Status.MediaAttachment[] attachments,
|
||||
boolean sensitive, final StatusActionListener listener) {
|
||||
final NetworkImageView[] previews = {
|
||||
mediaPreview0,
|
||||
mediaPreview1,
|
||||
mediaPreview2,
|
||||
mediaPreview3
|
||||
};
|
||||
Context context = mediaPreview0.getContext();
|
||||
ImageLoader imageLoader = VolleySingleton.getInstance(context).getImageLoader();
|
||||
final int n = Math.min(attachments.length, Status.MAX_MEDIA_ATTACHMENTS);
|
||||
for (int i = 0; i < n; i++) {
|
||||
String previewUrl = attachments[i].getPreviewUrl();
|
||||
previews[i].setImageUrl(previewUrl, imageLoader);
|
||||
if (!sensitive) {
|
||||
previews[i].setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
previews[i].setVisibility(View.GONE);
|
||||
}
|
||||
final String url = attachments[i].getUrl();
|
||||
final Status.MediaAttachment.Type type = attachments[i].getType();
|
||||
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);
|
||||
for (int i = 0; i < n; i++) {
|
||||
previews[i].setVisibility(View.VISIBLE);
|
||||
}
|
||||
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].setImageUrl(null, imageLoader);
|
||||
previews[i].setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void hideSensitiveMediaWarning() {
|
||||
sensitiveMediaWarning.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void setupButtons(final StatusActionListener listener, final int position) {
|
||||
replyButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onReply(position);
|
||||
}
|
||||
});
|
||||
reblogButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onReblog(!reblogged, position);
|
||||
}
|
||||
});
|
||||
favouriteButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onFavourite(!favourited, position);
|
||||
}
|
||||
});
|
||||
moreButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onMore(v, position);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class FooterViewHolder extends RecyclerView.ViewHolder {
|
||||
private LinearLayout retryBar;
|
||||
private Button retry;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
public FooterViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
retryBar = (LinearLayout) itemView.findViewById(R.id.footer_retry_bar);
|
||||
retry = (Button) itemView.findViewById(R.id.footer_retry_button);
|
||||
progressBar = (ProgressBar) itemView.findViewById(R.id.footer_progress_bar);
|
||||
progressBar.setIndeterminate(true);
|
||||
}
|
||||
|
||||
public void setupButton(final FooterActionListener listener) {
|
||||
retry.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onLoadMore();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void showRetry(boolean show) {
|
||||
if (!show) {
|
||||
retryBar.setVisibility(View.GONE);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
retryBar.setVisibility(View.VISIBLE);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,42 +16,31 @@
|
|||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.DividerItemDecoration;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.PopupMenu;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.android.volley.AuthFailureError;
|
||||
import com.android.volley.Request;
|
||||
import com.android.volley.Response;
|
||||
import com.android.volley.VolleyError;
|
||||
import com.android.volley.toolbox.JsonArrayRequest;
|
||||
import com.android.volley.toolbox.JsonObjectRequest;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class TimelineFragment extends Fragment implements
|
||||
public class TimelineFragment extends SFragment implements
|
||||
SwipeRefreshLayout.OnRefreshListener, StatusActionListener, FooterActionListener {
|
||||
|
||||
public enum Kind {
|
||||
|
@ -60,12 +49,6 @@ public class TimelineFragment extends Fragment implements
|
|||
PUBLIC,
|
||||
}
|
||||
|
||||
private String domain = null;
|
||||
private String accessToken = null;
|
||||
/** ID of the account that is currently logged-in. */
|
||||
private String userAccountId = null;
|
||||
/** Username of the account that is currently logged-in. */
|
||||
private String userUsername = null;
|
||||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
private RecyclerView recyclerView;
|
||||
private TimelineAdapter adapter;
|
||||
|
@ -90,15 +73,8 @@ public class TimelineFragment extends Fragment implements
|
|||
|
||||
View rootView = inflater.inflate(R.layout.fragment_timeline, container, false);
|
||||
|
||||
Context context = getContext();
|
||||
SharedPreferences preferences = context.getSharedPreferences(
|
||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||
domain = preferences.getString("domain", null);
|
||||
accessToken = preferences.getString("accessToken", null);
|
||||
assert(domain != null);
|
||||
assert(accessToken != null);
|
||||
|
||||
// Setup the SwipeRefreshLayout.
|
||||
Context context = getContext();
|
||||
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh_layout);
|
||||
swipeRefreshLayout.setOnRefreshListener(this);
|
||||
// Setup the RecyclerView.
|
||||
|
@ -121,7 +97,6 @@ public class TimelineFragment extends Fragment implements
|
|||
} else {
|
||||
sendFetchTimelineRequest();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
recyclerView.addOnScrollListener(scrollListener);
|
||||
|
@ -143,7 +118,6 @@ public class TimelineFragment extends Fragment implements
|
|||
};
|
||||
layout.addOnTabSelectedListener(onTabSelectedListener);
|
||||
|
||||
sendUserInfoRequest();
|
||||
sendFetchTimelineRequest();
|
||||
|
||||
return rootView;
|
||||
|
@ -161,22 +135,6 @@ public class TimelineFragment extends Fragment implements
|
|||
scrollListener.reset();
|
||||
}
|
||||
|
||||
private void sendUserInfoRequest() {
|
||||
sendRequest(Request.Method.GET, getString(R.string.endpoint_verify_credentials), null,
|
||||
new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {
|
||||
try {
|
||||
userAccountId = response.getString("id");
|
||||
userUsername = response.getString("acct");
|
||||
} catch (JSONException e) {
|
||||
//TODO: Help
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void sendFetchTimelineRequest(final String fromId) {
|
||||
String endpoint;
|
||||
switch (kind) {
|
||||
|
@ -251,7 +209,7 @@ public class TimelineFragment extends Fragment implements
|
|||
RecyclerView.ViewHolder viewHolder =
|
||||
recyclerView.findViewHolderForAdapterPosition(adapter.getItemCount() - 1);
|
||||
if (viewHolder != null) {
|
||||
TimelineAdapter.FooterViewHolder holder = (TimelineAdapter.FooterViewHolder) viewHolder;
|
||||
FooterViewHolder holder = (FooterViewHolder) viewHolder;
|
||||
holder.showRetry(show);
|
||||
}
|
||||
}
|
||||
|
@ -260,163 +218,6 @@ public class TimelineFragment extends Fragment implements
|
|||
sendFetchTimelineRequest();
|
||||
}
|
||||
|
||||
private void sendRequest(
|
||||
int method, String endpoint, JSONObject parameters,
|
||||
@Nullable Response.Listener<JSONObject> responseListener) {
|
||||
if (responseListener == null) {
|
||||
// Use a dummy listener if one wasn't specified so the request can be constructed.
|
||||
responseListener = new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {}
|
||||
};
|
||||
}
|
||||
String url = "https://" + domain + endpoint;
|
||||
JsonObjectRequest request = new JsonObjectRequest(
|
||||
method, url, parameters, responseListener,
|
||||
new Response.ErrorListener() {
|
||||
@Override
|
||||
public void onErrorResponse(VolleyError error) {
|
||||
System.err.println(error.getMessage());
|
||||
}
|
||||
}) {
|
||||
@Override
|
||||
public Map<String, String> getHeaders() throws AuthFailureError {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Authorization", "Bearer " + accessToken);
|
||||
return headers;
|
||||
}
|
||||
};
|
||||
VolleySingleton.getInstance(getContext()).addToRequestQueue(request);
|
||||
}
|
||||
|
||||
private void postRequest(String endpoint) {
|
||||
sendRequest(Request.Method.POST, endpoint, null, null);
|
||||
}
|
||||
|
||||
public void onReply(int position) {
|
||||
Status status = adapter.getItem(position);
|
||||
String inReplyToId = status.getId();
|
||||
Status.Mention[] mentions = status.getMentions();
|
||||
List<String> mentionedUsernames = new ArrayList<>();
|
||||
for (int i = 0; i < mentions.length; i++) {
|
||||
mentionedUsernames.add(mentions[i].getUsername());
|
||||
}
|
||||
mentionedUsernames.add(status.getUsername());
|
||||
mentionedUsernames.remove(userUsername);
|
||||
Intent intent = new Intent(getContext(), ComposeActivity.class);
|
||||
intent.putExtra("in_reply_to_id", inReplyToId);
|
||||
intent.putExtra("mentioned_usernames", mentionedUsernames.toArray(new String[0]));
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
public void onReblog(final boolean reblog, final int position) {
|
||||
final Status status = adapter.getItem(position);
|
||||
String id = status.getId();
|
||||
String endpoint;
|
||||
if (reblog) {
|
||||
endpoint = String.format(getString(R.string.endpoint_reblog), id);
|
||||
} else {
|
||||
endpoint = String.format(getString(R.string.endpoint_unreblog), id);
|
||||
}
|
||||
sendRequest(Request.Method.POST, endpoint, null,
|
||||
new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {
|
||||
status.setReblogged(reblog);
|
||||
adapter.notifyItemChanged(position);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onFavourite(final boolean favourite, final int position) {
|
||||
final Status status = adapter.getItem(position);
|
||||
String id = status.getId();
|
||||
String endpoint;
|
||||
if (favourite) {
|
||||
endpoint = String.format(getString(R.string.endpoint_favourite), id);
|
||||
} else {
|
||||
endpoint = String.format(getString(R.string.endpoint_unfavourite), id);
|
||||
}
|
||||
sendRequest(Request.Method.POST, endpoint, null, new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {
|
||||
status.setFavourited(favourite);
|
||||
adapter.notifyItemChanged(position);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void follow(String id) {
|
||||
String endpoint = String.format(getString(R.string.endpoint_follow), id);
|
||||
postRequest(endpoint);
|
||||
}
|
||||
|
||||
private void block(String id) {
|
||||
String endpoint = String.format(getString(R.string.endpoint_block), id);
|
||||
postRequest(endpoint);
|
||||
}
|
||||
|
||||
private void delete(String id) {
|
||||
String endpoint = String.format(getString(R.string.endpoint_delete), id);
|
||||
sendRequest(Request.Method.DELETE, endpoint, null, null);
|
||||
}
|
||||
|
||||
public void onMore(View view, final int position) {
|
||||
Status status = adapter.getItem(position);
|
||||
final String id = status.getId();
|
||||
final String accountId = status.getAccountId();
|
||||
PopupMenu popup = new PopupMenu(getContext(), view);
|
||||
// Give a different menu depending on whether this is the user's own toot or not.
|
||||
if (userAccountId == null || !userAccountId.equals(accountId)) {
|
||||
popup.inflate(R.menu.status_more);
|
||||
} else {
|
||||
popup.inflate(R.menu.status_more_for_user);
|
||||
}
|
||||
popup.setOnMenuItemClickListener(
|
||||
new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.status_follow: {
|
||||
follow(accountId);
|
||||
return true;
|
||||
}
|
||||
case R.id.status_block: {
|
||||
block(accountId);
|
||||
return true;
|
||||
}
|
||||
case R.id.status_delete: {
|
||||
delete(id);
|
||||
adapter.removeItem(position);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
popup.show();
|
||||
}
|
||||
|
||||
public void onViewMedia(String url, Status.MediaAttachment.Type type) {
|
||||
switch (type) {
|
||||
case IMAGE: {
|
||||
Fragment newFragment = ViewMediaFragment.newInstance(url);
|
||||
FragmentManager manager = getFragmentManager();
|
||||
manager.beginTransaction()
|
||||
.add(R.id.overlay_fragment_container, newFragment)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
break;
|
||||
}
|
||||
case VIDEO: {
|
||||
Intent intent = new Intent(getContext(), ViewVideoActivity.class);
|
||||
intent.putExtra("url", url);
|
||||
startActivity(intent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onLoadMore() {
|
||||
Status status = adapter.getItem(adapter.getItemCount() - 2);
|
||||
if (status != null) {
|
||||
|
@ -425,4 +226,28 @@ public class TimelineFragment extends Fragment implements
|
|||
sendFetchTimelineRequest();
|
||||
}
|
||||
}
|
||||
|
||||
public void onReply(int position) {
|
||||
super.reply(adapter.getItem(position));
|
||||
}
|
||||
|
||||
public void onReblog(final boolean reblog, final int position) {
|
||||
super.reblog(adapter.getItem(position), reblog, adapter, position);
|
||||
}
|
||||
|
||||
public void onFavourite(final boolean favourite, final int position) {
|
||||
super.favourite(adapter.getItem(position), favourite, adapter, position);
|
||||
}
|
||||
|
||||
public void onMore(View view, final int position) {
|
||||
super.more(adapter.getItem(position), view, adapter, position);
|
||||
}
|
||||
|
||||
public void onViewMedia(String url, Status.MediaAttachment.Type type) {
|
||||
super.viewMedia(url, type);
|
||||
}
|
||||
|
||||
public void onViewThread(int position) {
|
||||
super.viewThread(adapter.getItem(position));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is part of Tusky.
|
||||
*
|
||||
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||
* <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
public class ViewThreadActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_view_thread);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar bar = getSupportActionBar();
|
||||
if (bar != null) {
|
||||
bar.setTitle(R.string.title_thread);
|
||||
}
|
||||
|
||||
String id = getIntent().getStringExtra("id");
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
Fragment fragment = ViewThreadFragment.newInstance(id);
|
||||
fragmentTransaction.add(R.id.fragment_container, fragment);
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.view_thread_toolbar, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_back: {
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is part of Tusky.
|
||||
*
|
||||
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||
* <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.DividerItemDecoration;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.android.volley.Request;
|
||||
import com.android.volley.Response;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ViewThreadFragment extends SFragment implements StatusActionListener {
|
||||
private RecyclerView recyclerView;
|
||||
private ThreadAdapter adapter;
|
||||
|
||||
public static ViewThreadFragment newInstance(String id) {
|
||||
Bundle arguments = new Bundle();
|
||||
ViewThreadFragment fragment = new ViewThreadFragment();
|
||||
arguments.putString("id", id);
|
||||
fragment.setArguments(arguments);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_view_thread, container, false);
|
||||
|
||||
Context context = getContext();
|
||||
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
|
||||
recyclerView.setHasFixedSize(true);
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
DividerItemDecoration divider = new DividerItemDecoration(
|
||||
context, layoutManager.getOrientation());
|
||||
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.status_divider);
|
||||
divider.setDrawable(drawable);
|
||||
recyclerView.addItemDecoration(divider);
|
||||
adapter = new ThreadAdapter(this);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
String id = getArguments().getString("id");
|
||||
sendStatusRequest(id);
|
||||
sendThreadRequest(id);
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void sendStatusRequest(String id) {
|
||||
String endpoint = String.format(getString(R.string.endpoint_get_status), id);
|
||||
super.sendRequest(Request.Method.GET, endpoint, null,
|
||||
new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {
|
||||
Status status;
|
||||
try {
|
||||
status = Status.parse(response, false);
|
||||
} catch (JSONException e) {
|
||||
onThreadRequestFailure();
|
||||
return;
|
||||
}
|
||||
int position = adapter.insertStatus(status);
|
||||
recyclerView.scrollToPosition(position);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void sendThreadRequest(String id) {
|
||||
String endpoint = String.format(getString(R.string.endpoint_context), id);
|
||||
super.sendRequest(Request.Method.GET, endpoint, null,
|
||||
new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {
|
||||
try {
|
||||
List<Status> ancestors =
|
||||
Status.parse(response.getJSONArray("ancestors"));
|
||||
List<Status> descendants =
|
||||
Status.parse(response.getJSONArray("descendants"));
|
||||
adapter.addAncestors(ancestors);
|
||||
adapter.addDescendants(descendants);
|
||||
} catch (JSONException e) {
|
||||
onThreadRequestFailure();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onThreadRequestFailure() {
|
||||
//TODO: no
|
||||
assert(false);
|
||||
}
|
||||
|
||||
public void onReply(int position) {
|
||||
super.reply(adapter.getItem(position));
|
||||
}
|
||||
|
||||
public void onReblog(boolean reblog, int position) {
|
||||
super.reblog(adapter.getItem(position), reblog, adapter, position);
|
||||
}
|
||||
|
||||
public void onFavourite(boolean favourite, int position) {
|
||||
super.favourite(adapter.getItem(position), favourite, adapter, position);
|
||||
}
|
||||
|
||||
public void onMore(View view, int position) {
|
||||
super.more(adapter.getItem(position), view, adapter, position);
|
||||
}
|
||||
|
||||
public void onViewMedia(String url, Status.MediaAttachment.Type type) {
|
||||
super.viewMedia(url, type);
|
||||
}
|
||||
|
||||
public void onViewThread(int position) {
|
||||
super.viewThread(adapter.getItem(position));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue