parent
b78ccb1b49
commit
370b1e52aa
@ -0,0 +1,137 @@ |
||||
package com.keylesspalace.tusky; |
||||
|
||||
import android.content.Context; |
||||
import android.content.SharedPreferences; |
||||
import android.os.Bundle; |
||||
import android.support.v7.app.AppCompatActivity; |
||||
import android.text.Editable; |
||||
import android.text.TextWatcher; |
||||
import android.view.View; |
||||
import android.widget.Button; |
||||
import android.widget.EditText; |
||||
import android.widget.RadioGroup; |
||||
import android.widget.TextView; |
||||
import android.widget.Toast; |
||||
|
||||
import com.android.volley.AuthFailureError; |
||||
import com.android.volley.Request; |
||||
import com.android.volley.Response; |
||||
import com.android.volley.VolleyError; |
||||
import com.android.volley.toolbox.JsonObjectRequest; |
||||
|
||||
import org.json.JSONException; |
||||
import org.json.JSONObject; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
public class ComposeActivity extends AppCompatActivity { |
||||
private static int STATUS_CHARACTER_LIMIT = 500; |
||||
|
||||
private String domain; |
||||
private String accessToken; |
||||
private EditText textEditor; |
||||
|
||||
private void onSendSuccess() { |
||||
Toast.makeText(this, "Toot!", Toast.LENGTH_SHORT).show(); |
||||
finish(); |
||||
} |
||||
|
||||
private void onSendFailure(Exception exception) { |
||||
textEditor.setError(getString(R.string.error_sending_status)); |
||||
} |
||||
|
||||
private void sendStatus(String content, String visibility) { |
||||
String endpoint = getString(R.string.endpoint_status); |
||||
String url = "https://" + domain + endpoint; |
||||
JSONObject parameters = new JSONObject(); |
||||
try { |
||||
parameters.put("status", content); |
||||
parameters.put("visibility", visibility); |
||||
} catch (JSONException e) { |
||||
onSendFailure(e); |
||||
return; |
||||
} |
||||
JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, url, parameters, |
||||
new Response.Listener<JSONObject>() { |
||||
@Override |
||||
public void onResponse(JSONObject response) { |
||||
onSendSuccess(); |
||||
} |
||||
}, new Response.ErrorListener() { |
||||
@Override |
||||
public void onErrorResponse(VolleyError error) { |
||||
onSendFailure(error); |
||||
} |
||||
}) { |
||||
@Override |
||||
public Map<String, String> getHeaders() throws AuthFailureError { |
||||
Map<String, String> headers = new HashMap<>(); |
||||
headers.put("Authorization", "Bearer " + accessToken); |
||||
return headers; |
||||
} |
||||
}; |
||||
VolleySingleton.getInstance(this).addToRequestQueue(request); |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(Bundle savedInstanceState) { |
||||
super.onCreate(savedInstanceState); |
||||
setContentView(R.layout.activity_compose); |
||||
|
||||
SharedPreferences preferences = 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); |
||||
|
||||
textEditor = (EditText) findViewById(R.id.field_status); |
||||
final TextView charactersLeft = (TextView) findViewById(R.id.characters_left); |
||||
TextWatcher textEditorWatcher = new TextWatcher() { |
||||
@Override |
||||
public void onTextChanged(CharSequence s, int start, int before, int count) { |
||||
int left = STATUS_CHARACTER_LIMIT - s.length(); |
||||
charactersLeft.setText(Integer.toString(left)); |
||||
} |
||||
|
||||
@Override |
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {} |
||||
|
||||
@Override |
||||
public void afterTextChanged(Editable s) {} |
||||
}; |
||||
textEditor.addTextChangedListener(textEditorWatcher); |
||||
|
||||
final RadioGroup radio = (RadioGroup) findViewById(R.id.radio_visibility); |
||||
final Button sendButton = (Button) findViewById(R.id.button_send); |
||||
sendButton.setOnClickListener(new View.OnClickListener() { |
||||
@Override |
||||
public void onClick(View v) { |
||||
Editable editable = textEditor.getText(); |
||||
if (editable.length() <= STATUS_CHARACTER_LIMIT) { |
||||
int id = radio.getCheckedRadioButtonId(); |
||||
String visibility; |
||||
switch (id) { |
||||
default: |
||||
case R.id.radio_public: { |
||||
visibility = "public"; |
||||
break; |
||||
} |
||||
case R.id.radio_unlisted: { |
||||
visibility = "unlisted"; |
||||
break; |
||||
} |
||||
case R.id.radio_private: { |
||||
visibility = "private"; |
||||
break; |
||||
} |
||||
} |
||||
sendStatus(editable.toString(), visibility); |
||||
} else { |
||||
textEditor.setError(getString(R.string.error_compose_character_limit)); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
package com.keylesspalace.tusky; |
||||
|
||||
import android.support.annotation.Nullable; |
||||
|
||||
public class Notification { |
||||
public enum Type { |
||||
MENTION, |
||||
REBLOG, |
||||
FAVOURITE, |
||||
FOLLOW, |
||||
} |
||||
private Type type; |
||||
private String id; |
||||
private String displayName; |
||||
/** Which of the user's statuses has been mentioned, reblogged, or favourited. */ |
||||
private Status status; |
||||
|
||||
public Notification(Type type, String id, String displayName) { |
||||
this.type = type; |
||||
this.id = id; |
||||
this.displayName = displayName; |
||||
} |
||||
|
||||
public Type getType() { |
||||
return type; |
||||
} |
||||
|
||||
public String getId() { |
||||
return id; |
||||
} |
||||
|
||||
public String getDisplayName() { |
||||
return displayName; |
||||
} |
||||
|
||||
public @Nullable Status getStatus() { |
||||
return status; |
||||
} |
||||
|
||||
public void setStatus(Status status) { |
||||
this.status = status; |
||||
} |
||||
} |
@ -0,0 +1,99 @@ |
||||
package com.keylesspalace.tusky; |
||||
|
||||
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 java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
public class NotificationsAdapter extends RecyclerView.Adapter { |
||||
private List<Notification> notifications = new ArrayList<>(); |
||||
|
||||
@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); |
||||
} |
||||
|
||||
@Override |
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { |
||||
ViewHolder holder = (ViewHolder) viewHolder; |
||||
Notification notification = notifications.get(position); |
||||
holder.setMessage(notification.getType(), notification.getDisplayName()); |
||||
} |
||||
|
||||
@Override |
||||
public int getItemCount() { |
||||
return notifications.size(); |
||||
} |
||||
|
||||
public Notification getItem(int position) { |
||||
return notifications.get(position); |
||||
} |
||||
|
||||
public int update(List<Notification> new_notifications) { |
||||
int scrollToPosition; |
||||
if (notifications == null || notifications.isEmpty()) { |
||||
notifications = new_notifications; |
||||
scrollToPosition = 0; |
||||
} else { |
||||
int index = new_notifications.indexOf(notifications.get(0)); |
||||
if (index == -1) { |
||||
notifications.addAll(0, new_notifications); |
||||
scrollToPosition = 0; |
||||
} else { |
||||
notifications.addAll(0, new_notifications.subList(0, index)); |
||||
scrollToPosition = index; |
||||
} |
||||
} |
||||
notifyDataSetChanged(); |
||||
return scrollToPosition; |
||||
} |
||||
|
||||
public void addItems(List<Notification> new_notifications) { |
||||
int end = notifications.size(); |
||||
notifications.addAll(new_notifications); |
||||
notifyItemRangeInserted(end, new_notifications.size()); |
||||
} |
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder { |
||||
private TextView message; |
||||
|
||||
public ViewHolder(View itemView) { |
||||
super(itemView); |
||||
message = (TextView) itemView.findViewById(R.id.notification_text); |
||||
} |
||||
|
||||
public void setMessage(Notification.Type type, 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; |
||||
} |
||||
} |
||||
message.setText(wholeMessage); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,157 @@ |
||||
package com.keylesspalace.tusky; |
||||
|
||||
import android.content.Context; |
||||
import android.content.SharedPreferences; |
||||
import android.graphics.drawable.Drawable; |
||||
import android.os.Bundle; |
||||
import android.support.annotation.Nullable; |
||||
import android.support.v4.app.Fragment; |
||||
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.RecyclerView; |
||||
import android.view.LayoutInflater; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
import android.widget.Toast; |
||||
|
||||
import com.android.volley.AuthFailureError; |
||||
import com.android.volley.Response; |
||||
import com.android.volley.VolleyError; |
||||
import com.android.volley.toolbox.JsonArrayRequest; |
||||
|
||||
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 NotificationsFragment extends Fragment implements |
||||
SwipeRefreshLayout.OnRefreshListener { |
||||
private String domain = null; |
||||
private String accessToken = null; |
||||
private SwipeRefreshLayout swipeRefreshLayout; |
||||
private NotificationsAdapter adapter; |
||||
|
||||
public static NotificationsFragment newInstance() { |
||||
NotificationsFragment fragment = new NotificationsFragment(); |
||||
Bundle arguments = new Bundle(); |
||||
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_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.
|
||||
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh_layout); |
||||
swipeRefreshLayout.setOnRefreshListener(this); |
||||
// Setup the RecyclerView.
|
||||
RecyclerView 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); |
||||
EndlessOnScrollListener scrollListener = new EndlessOnScrollListener(layoutManager) { |
||||
@Override |
||||
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) { |
||||
NotificationsAdapter adapter = (NotificationsAdapter) view.getAdapter(); |
||||
String fromId = adapter.getItem(adapter.getItemCount() - 1).getId(); |
||||
sendFetchNotificationsRequest(fromId); |
||||
} |
||||
}; |
||||
recyclerView.addOnScrollListener(scrollListener); |
||||
adapter = new NotificationsAdapter(); |
||||
recyclerView.setAdapter(adapter); |
||||
|
||||
sendFetchNotificationsRequest(); |
||||
|
||||
return rootView; |
||||
} |
||||
|
||||
private void sendFetchNotificationsRequest(final String fromId) { |
||||
String endpoint = getString(R.string.endpoint_notifications); |
||||
String url = "https://" + domain + endpoint; |
||||
if (fromId != null) { |
||||
url += "?max_id=" + fromId; |
||||
} |
||||
JsonArrayRequest request = new JsonArrayRequest(url, |
||||
new Response.Listener<JSONArray>() { |
||||
@Override |
||||
public void onResponse(JSONArray response) { |
||||
List<Notification> notifications = new ArrayList<>(); |
||||
try { |
||||
for (int i = 0; i < response.length(); i++) { |
||||
JSONObject object = response.getJSONObject(i); |
||||
String id = object.getString("id"); |
||||
Notification.Type type = Notification.Type.valueOf( |
||||
object.getString("type").toUpperCase()); |
||||
JSONObject account = object.getJSONObject("account"); |
||||
String displayName = account.getString("display_name"); |
||||
Notification notification = new Notification(type, id, displayName); |
||||
notifications.add(notification); |
||||
} |
||||
onFetchNotificationsSuccess(notifications, fromId != null); |
||||
} catch (JSONException e) { |
||||
onFetchNotificationsFailure(e); |
||||
} |
||||
} |
||||
}, new Response.ErrorListener() { |
||||
@Override |
||||
public void onErrorResponse(VolleyError error) { |
||||
onFetchNotificationsFailure(error); |
||||
} |
||||
}) { |
||||
@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 sendFetchNotificationsRequest() { |
||||
sendFetchNotificationsRequest(null); |
||||
} |
||||
|
||||
private void onFetchNotificationsSuccess(List<Notification> notifications, boolean added) { |
||||
if (added) { |
||||
adapter.addItems(notifications); |
||||
} else { |
||||
adapter.update(notifications); |
||||
} |
||||
swipeRefreshLayout.setRefreshing(false); |
||||
} |
||||
|
||||
private void onFetchNotificationsFailure(Exception exception) { |
||||
Toast.makeText(getContext(), R.string.error_fetching_notifications, Toast.LENGTH_SHORT) |
||||
.show(); |
||||
swipeRefreshLayout.setRefreshing(false); |
||||
} |
||||
|
||||
@Override |
||||
public void onRefresh() { |
||||
sendFetchNotificationsRequest(); |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
package com.keylesspalace.tusky; |
||||
|
||||
import android.view.View; |
||||
|
||||
public interface StatusActionListener { |
||||
void onReblog(final boolean reblog, final int position); |
||||
void onFavourite(final boolean favourite, final int position); |
||||
void onMore(View view, final int position); |
||||
} |
@ -0,0 +1,315 @@ |
||||
package com.keylesspalace.tusky; |
||||
|
||||
import android.content.Context; |
||||
import android.content.SharedPreferences; |
||||
import android.graphics.drawable.Drawable; |
||||
import android.os.Bundle; |
||||
import android.support.annotation.Nullable; |
||||
import android.support.v4.app.Fragment; |
||||
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 android.widget.Toast; |
||||
|
||||
import com.android.volley.AuthFailureError; |
||||
import com.android.volley.Request; |
||||
import com.android.volley.Response; |
||||
import com.android.volley.VolleyError; |
||||
import com.android.volley.toolbox.JsonArrayRequest; |
||||
import com.android.volley.toolbox.JsonObjectRequest; |
||||
|
||||
import org.json.JSONArray; |
||||
import org.json.JSONException; |
||||
import org.json.JSONObject; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
public class TimelineFragment extends Fragment implements |
||||
SwipeRefreshLayout.OnRefreshListener, StatusActionListener { |
||||
|
||||
public enum Kind { |
||||
HOME, |
||||
MENTIONS, |
||||
PUBLIC, |
||||
} |
||||
|
||||
private String domain = null; |
||||
private String accessToken = null; |
||||
private String userAccountId = null; |
||||
private SwipeRefreshLayout swipeRefreshLayout; |
||||
private TimelineAdapter adapter; |
||||
private Kind kind; |
||||
|
||||
public static TimelineFragment newInstance(Kind kind) { |
||||
TimelineFragment fragment = new TimelineFragment(); |
||||
Bundle arguments = new Bundle(); |
||||
arguments.putString("kind", kind.name()); |
||||
fragment.setArguments(arguments); |
||||
return fragment; |
||||
} |
||||
|
||||
@Override |
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, |
||||
Bundle savedInstanceState) { |
||||
|
||||
kind = Kind.valueOf(getArguments().getString("kind")); |
||||
|
||||
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.
|
||||
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh_layout); |
||||
swipeRefreshLayout.setOnRefreshListener(this); |
||||
// Setup the RecyclerView.
|
||||
RecyclerView 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); |
||||
EndlessOnScrollListener scrollListener = new EndlessOnScrollListener(layoutManager) { |
||||
@Override |
||||
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) { |
||||
TimelineAdapter adapter = (TimelineAdapter) view.getAdapter(); |
||||
String fromId = adapter.getItem(adapter.getItemCount() - 1).getId(); |
||||
sendFetchTimelineRequest(fromId); |
||||
} |
||||
}; |
||||
recyclerView.addOnScrollListener(scrollListener); |
||||
adapter = new TimelineAdapter(this); |
||||
recyclerView.setAdapter(adapter); |
||||
|
||||
sendUserInfoRequest(); |
||||
sendFetchTimelineRequest(); |
||||
|
||||
return rootView; |
||||
} |
||||
|
||||
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"); |
||||
} catch (JSONException e) { |
||||
//TODO: Help
|
||||
assert(false); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private void sendFetchTimelineRequest(final String fromId) { |
||||
String endpoint; |
||||
switch (kind) { |
||||
default: |
||||
case HOME: { |
||||
endpoint = getString(R.string.endpoint_timelines_home); |
||||
break; |
||||
} |
||||
case MENTIONS: { |
||||
endpoint = getString(R.string.endpoint_timelines_mentions); |
||||
break; |
||||
} |
||||
case PUBLIC: { |
||||
endpoint = getString(R.string.endpoint_timelines_public); |
||||
break; |
||||
} |
||||
} |
||||
String url = "https://" + domain + endpoint; |
||||
if (fromId != null) { |
||||
url += "?max_id=" + fromId; |
||||
} |
||||
JsonArrayRequest request = new JsonArrayRequest(url, |
||||
new Response.Listener<JSONArray>() { |
||||
@Override |
||||
public void onResponse(JSONArray response) { |
||||
List<Status> statuses = null; |
||||
try { |
||||
statuses = Status.parse(response); |
||||
} catch (JSONException e) { |
||||
onFetchTimelineFailure(e); |
||||
} |
||||
if (statuses != null) { |
||||
onFetchTimelineSuccess(statuses, fromId != null); |
||||
} |
||||
} |
||||
}, new Response.ErrorListener() { |
||||
@Override |
||||
public void onErrorResponse(VolleyError error) { |
||||
onFetchTimelineFailure(error); |
||||
} |
||||
}) { |
||||
@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 sendFetchTimelineRequest() { |
||||
sendFetchTimelineRequest(null); |
||||
} |
||||
|
||||
public void onFetchTimelineSuccess(List<Status> statuses, boolean added) { |
||||
if (added) { |
||||
adapter.addItems(statuses); |
||||
} else { |
||||
adapter.update(statuses); |
||||
} |
||||
swipeRefreshLayout.setRefreshing(false); |
||||
} |
||||
|
||||
public void onFetchTimelineFailure(Exception exception) { |
||||
Toast.makeText(getContext(), R.string.error_fetching_timeline, Toast.LENGTH_SHORT).show(); |
||||
swipeRefreshLayout.setRefreshing(false); |
||||
} |
||||
|
||||
public void onRefresh() { |
||||
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 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(); |
||||
} |
||||
} |
@ -0,0 +1,45 @@ |
||||
package com.keylesspalace.tusky; |
||||
|
||||
import android.support.v4.app.Fragment; |
||||
import android.support.v4.app.FragmentManager; |
||||
import android.support.v4.app.FragmentPagerAdapter; |
||||
|
||||
public class TimelinePagerAdapter extends FragmentPagerAdapter { |
||||
private String[] pageTitles; |
||||
|
||||
public TimelinePagerAdapter(FragmentManager manager) { |
||||
super(manager); |
||||
} |
||||
|
||||
public void setPageTitles(String[] titles) { |
||||
pageTitles = titles; |
||||
} |
||||
|
||||
@Override |
||||
public Fragment getItem(int i) { |
||||
switch (i) { |
||||
case 0: { |
||||
return TimelineFragment.newInstance(TimelineFragment.Kind.HOME); |
||||
} |
||||
case 1: { |
||||
return NotificationsFragment.newInstance(); |
||||
} |
||||
case 2: { |
||||
return TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC); |
||||
} |
||||
default: { |
||||
return null; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public int getCount() { |
||||
return 3; |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence getPageTitle(int position) { |
||||
return pageTitles[position]; |
||||
} |
||||
} |
@ -0,0 +1,11 @@ |
||||
<vector android:height="24dp" android:viewportHeight="708.66144" |
||||
android:viewportWidth="708.66144" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<path android:fillAlpha="1" android:fillColor="#ffffff" |
||||
android:pathData="m86.4,17.7c-37.3,0 -67.3,30 -67.3,67.3l0,538.6c0,37.3 30,67.3 67.3,67.3l537.2,0c37.3,0 67.3,-30 67.3,-67.3l0,-462.4c-17.6,17.9 -35.4,35.6 -53.2,53.3l0,352.4c0,39.3 -31.6,70.9 -70.9,70.9l-425.2,0c-39.3,0 -70.9,-31.6 -70.9,-70.9l0,-425.2c0,-39.3 31.6,-70.9 70.9,-70.9l358.9,0c18,-17.7 36,-35.3 53.8,-53.2l-468,0z" |
||||
android:strokeAlpha="1" android:strokeColor="#ffffff" |
||||
android:strokeLineCap="butt" android:strokeLineJoin="round" android:strokeWidth="33.62945938"/> |
||||
<path android:fillAlpha="1" android:fillColor="#ffffff" |
||||
android:pathData="m672.8,8.2 l25.1,25.1c13.9,13.9 13.9,36.2 0,50.1L361.6,420.4C347.1,434.2 199.5,537.2 185.6,523.3l-0.8,-0.8C170.9,508.6 272.1,359.2 286.6,344.7L622.7,8.2c13.9,-13.9 36.2,-13.9 50.1,0z" |
||||
android:strokeAlpha="1" android:strokeColor="#cccccc" |
||||
android:strokeLineCap="butt" android:strokeLineJoin="round" android:strokeWidth="0"/> |
||||
</vector> |
@ -0,0 +1,15 @@ |
||||
<vector android:height="24dp" android:viewportHeight="708.66144" |
||||
android:viewportWidth="708.66144" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<path android:fillAlpha="1" android:fillColor="#4d4d4d" |
||||
android:pathData="M177.2,354.3m-70.9,0a70.9,70.9 0,1 1,141.7 0a70.9,70.9 0,1 1,-141.7 0" |
||||
android:strokeAlpha="1" android:strokeColor="#6d6d6d" |
||||
android:strokeLineCap="butt" android:strokeLineJoin="round" android:strokeWidth="14.17322826"/> |
||||
<path android:fillAlpha="1" android:fillColor="#4d4d4d" |
||||
android:pathData="M354.3,354.3m-70.9,0a70.9,70.9 0,1 1,141.7 0a70.9,70.9 0,1 1,-141.7 0" |
||||
android:strokeAlpha="1" android:strokeColor="#6d6d6d" |
||||
android:strokeLineCap="butt" android:strokeLineJoin="round" android:strokeWidth="14.17322826"/> |
||||
<path android:fillAlpha="1" android:fillColor="#4d4d4d" |
||||
android:pathData="M531.5,354.3m-70.9,0a70.9,70.9 0,1 1,141.7 0a70.9,70.9 0,1 1,-141.7 0" |
||||
android:strokeAlpha="1" android:strokeColor="#6d6d6d" |
||||
android:strokeLineCap="butt" android:strokeLineJoin="round" android:strokeWidth="14.17322826"/> |
||||
</vector> |
@ -0,0 +1,7 @@ |
||||
<vector android:height="24dp" android:viewportHeight="708.66144" |
||||
android:viewportWidth="708.66144" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<path android:fillAlpha="1" android:fillColor="#4d4d4d" |
||||
android:pathData="M354.3,33.9 L458.4,244.9 691.2,278.7 522.8,442.9 562.5,674.7 354.3,565.3 146.1,674.7 185.9,442.9 17.5,278.7 250.2,244.9Z" |
||||
android:strokeAlpha="1" android:strokeColor="#6d6d6d" |
||||
android:strokeLineCap="butt" android:strokeLineJoin="round" android:strokeWidth="33.33507919"/> |
||||
</vector> |
@ -0,0 +1,7 @@ |
||||
<vector android:height="24dp" android:viewportHeight="708.66144" |
||||
android:viewportWidth="708.66144" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<path android:fillAlpha="1" android:fillColor="#feef00" |
||||
android:pathData="M354.3,33.9 L458.4,244.9 691.2,278.7 522.8,442.9 562.5,674.7 354.3,565.3 146.1,674.7 185.9,442.9 17.5,278.7 250.2,244.9Z" |
||||
android:strokeAlpha="1" android:strokeColor="#feef00" |
||||
android:strokeLineCap="butt" android:strokeLineJoin="round" android:strokeWidth="33.33507919"/> |
||||
</vector> |
@ -0,0 +1,11 @@ |
||||
<vector android:height="24dp" android:viewportHeight="708.66144" |
||||
android:viewportWidth="708.66144" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<path android:fillColor="#cccccc" |
||||
android:pathData="M141.1,141.7L-0.6,283.5L105.7,283.5L105.7,527.5L176.5,456.7L176.5,283.5L282.8,283.5L141.1,141.7zM213.5,141.7L284.4,212.6L420.6,212.6L491.5,141.7L213.5,141.7zM603.3,182.8L532.4,253.7L532.4,460.6L426.1,460.6L567.9,602.4L709.6,460.6L603.3,460.6L603.3,182.8zM254.6,531.5L183.8,602.4L495.4,602.4L424.6,531.5L254.6,531.5z" |
||||
android:strokeAlpha="1" android:strokeColor="#cccccc" |
||||
android:strokeLineCap="butt" android:strokeLineJoin="round" android:strokeWidth="15"/> |
||||
<path android:fillAlpha="1" android:fillColor="#cccccc" |
||||
android:pathData="M680,28.6L680,28.6A35.4,35.4 76.1,0 1,680 78.7L78.7,680A35.4,35.4 90.3,0 1,28.6 680L28.6,680A35.4,35.4 90.3,0 1,28.6 629.9L629.9,28.6A35.4,35.4 76.1,0 1,680 28.6z" |
||||
android:strokeAlpha="1" android:strokeColor="#bebebe" |
||||
android:strokeLineCap="butt" android:strokeLineJoin="round" android:strokeWidth="0"/> |
||||
</vector> |
@ -0,0 +1,11 @@ |
||||
<vector android:height="24dp" android:viewportHeight="708.66144" |
||||
android:viewportWidth="708.66144" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<path android:fillColor="#4d4d4d" |
||||
android:pathData="m567.9,602.4 l-141.7,-141.7 106.3,0 0,-248 -248,0 -70.9,-70.9 389.8,0 0,318.9 106.3,0z" |
||||
android:strokeAlpha="1" android:strokeColor="#6d6d6d" |
||||
android:strokeLineCap="butt" android:strokeLineJoin="round" android:strokeWidth="14.17322835"/> |
||||
<path android:fillColor="#4d4d4d" |
||||
android:pathData="m141.1,141.7 l141.7,141.7 -106.3,0 0,248 248,0 70.9,70.9 -389.8,0 0,-318.9 -106.3,0z" |
||||
android:strokeAlpha="1" android:strokeColor="#6d6d6d" |
||||
android:strokeLineCap="butt" android:strokeLineJoin="round" android:strokeWidth="14.17322826"/> |
||||
</vector> |
@ -0,0 +1,11 @@ |
||||
<vector android:height="24dp" android:viewportHeight="708.66144" |
||||
android:viewportWidth="708.66144" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<path android:fillColor="#008080" |
||||
android:pathData="m567.9,602.4 l-141.7,-141.7 106.3,0 0,-248 -248,0 -70.9,-70.9 389.8,0 0,318.9 106.3,0z" |
||||
android:strokeAlpha="1" android:strokeColor="#3c9b9e" |
||||
android:strokeLineCap="butt" android:strokeLineJoin="round" android:strokeWidth="14.173"/> |
||||
<path android:fillColor="#008080" |
||||
android:pathData="m141.1,141.7 l141.7,141.7 -106.3,0 0,248 248,0 70.9,70.9 -389.8,0 0,-318.9 -106.3,0z" |
||||
android:strokeAlpha="1" android:strokeColor="#3c9b9e" |
||||
android:strokeLineCap="butt" android:strokeLineJoin="round" android:strokeWidth="14.17322826"/> |
||||
</vector> |
@ -0,0 +1,7 @@ |
||||
<vector android:height="24dp" android:viewportHeight="708.66144" |
||||
android:viewportWidth="708.66144" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<path android:fillAlpha="1" android:fillColor="#4d4d4d" |
||||
android:pathData="m40.4,348.7c200,-200 200,-200 200,-200l0,120 440,0 0,320 -160,0 0,-160 -280,0 0,120z" |
||||
android:strokeAlpha="1" android:strokeColor="#6d6d6d" |
||||
android:strokeLineCap="butt" android:strokeLineJoin="round" android:strokeWidth="21.25984252"/> |
||||
</vector> |
@ -0,0 +1,69 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:orientation="vertical" android:layout_width="match_parent" |
||||
android:layout_height="match_parent"> |
||||
|
||||
<EditText |
||||
android:layout_width="match_parent" |
||||
android:inputType="textMultiLine" |
||||
android:ems="10" |
||||
android:gravity="top|left" |
||||
android:id="@+id/field_status" |
||||
android:contentDescription="What's Happening?" |
||||
android:layout_height="wrap_content" |
||||
android:layout_weight="1" /> |
||||
|
||||
<LinearLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content"> |
||||
|
||||
<RadioGroup |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="match_parent" |
||||
android:orientation="horizontal" |
||||
android:id="@+id/radio_visibility" |
||||
android:checkedButton="@+id/radio_public"> |
||||
|
||||
<RadioButton |
||||
android:text="@string/visibility_public" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="match_parent" |
||||
android:id="@+id/radio_public" |
||||
android:layout_weight="1" /> |
||||
|
||||
<RadioButton |
||||
android:text="@string/visibility_unlisted" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="match_parent" |
||||
android:id="@+id/radio_unlisted" |
||||
android:layout_weight="1" /> |
||||
|
||||
<RadioButton |
||||
android:text="@string/visibility_private" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="match_parent" |
||||
android:id="@+id/radio_private" |
||||
android:layout_weight="1" /> |
||||
|
||||
</RadioGroup> |
||||
|
||||
<Space |
||||
android:layout_width="0dp" |
||||
android:layout_height="match_parent" |
||||
android:layout_weight="1" /> |
||||
|
||||
<TextView |
||||
android:id="@+id/characters_left" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:text="500" /> |
||||
|
||||
<Button |
||||
android:text="TOOT" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:id="@+id/button_send" /> |
||||
|
||||
</LinearLayout> |
||||
|
||||
</LinearLayout> |
@ -0,0 +1,13 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<android.support.v4.widget.SwipeRefreshLayout |
||||
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:id="@+id/swipe_refresh_layout" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent"> |
||||
|
||||
<android.support.v7.widget.RecyclerView |
||||
android:id="@+id/recycler_view" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" /> |
||||
|
||||
</android.support.v4.widget.SwipeRefreshLayout> |
@ -0,0 +1,11 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:orientation="vertical" android:layout_width="match_parent" |
||||
android:layout_height="wrap_content"> |
||||
|
||||
<TextView |
||||
android:id="@+id/notification_text" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" /> |
||||
|
||||
</LinearLayout> |
@ -0,0 +1,8 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
|
||||
<item android:id="@+id/status_follow" |
||||
android:title="@string/action_follow" /> |
||||
<item android:title="@string/action_block" |
||||
android:id="@+id/status_block" /> |
||||
</menu> |
@ -0,0 +1,6 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
|
||||
<item android:title="@string/action_delete" |
||||
android:id="@+id/status_delete" /> |
||||
</menu> |
Loading…
Reference in new issue