TimelineFragment.java 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. /* Copyright 2017 Andrew Dawson
  2. *
  3. * This file is a part of Tusky.
  4. *
  5. * This program is free software; you can redistribute it and/or modify it under the terms of the
  6. * GNU General Public License as published by the Free Software Foundation; either version 3 of the
  7. * License, or (at your option) any later version.
  8. *
  9. * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
  10. * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
  11. * Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License along with Tusky; if not,
  14. * see <http://www.gnu.org/licenses>. */
  15. package com.keylesspalace.tusky;
  16. import android.content.Context;
  17. import android.graphics.drawable.Drawable;
  18. import android.os.Bundle;
  19. import android.support.annotation.Nullable;
  20. import android.support.design.widget.TabLayout;
  21. import android.support.v4.widget.SwipeRefreshLayout;
  22. import android.support.v7.widget.DividerItemDecoration;
  23. import android.support.v7.widget.LinearLayoutManager;
  24. import android.support.v7.widget.RecyclerView;
  25. import android.view.LayoutInflater;
  26. import android.view.View;
  27. import android.view.ViewGroup;
  28. import com.keylesspalace.tusky.entity.Status;
  29. import java.util.List;
  30. import retrofit2.Call;
  31. import retrofit2.Callback;
  32. public class TimelineFragment extends SFragment implements
  33. SwipeRefreshLayout.OnRefreshListener, StatusActionListener {
  34. private static final String TAG = "Timeline"; // logging tag
  35. private Call<List<Status>> listCall;
  36. enum Kind {
  37. HOME,
  38. PUBLIC_LOCAL,
  39. PUBLIC_FEDERATED,
  40. TAG,
  41. USER,
  42. FAVOURITES
  43. }
  44. private SwipeRefreshLayout swipeRefreshLayout;
  45. private TimelineAdapter adapter;
  46. private Kind kind;
  47. private String hashtagOrId;
  48. private LinearLayoutManager layoutManager;
  49. private EndlessOnScrollListener scrollListener;
  50. private TabLayout.OnTabSelectedListener onTabSelectedListener;
  51. public static TimelineFragment newInstance(Kind kind) {
  52. TimelineFragment fragment = new TimelineFragment();
  53. Bundle arguments = new Bundle();
  54. arguments.putString("kind", kind.name());
  55. fragment.setArguments(arguments);
  56. return fragment;
  57. }
  58. public static TimelineFragment newInstance(Kind kind, String hashtagOrId) {
  59. TimelineFragment fragment = new TimelineFragment();
  60. Bundle arguments = new Bundle();
  61. arguments.putString("kind", kind.name());
  62. arguments.putString("hashtag_or_id", hashtagOrId);
  63. fragment.setArguments(arguments);
  64. return fragment;
  65. }
  66. @Override
  67. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  68. Bundle savedInstanceState) {
  69. Bundle arguments = getArguments();
  70. kind = Kind.valueOf(arguments.getString("kind"));
  71. if (kind == Kind.TAG || kind == Kind.USER) {
  72. hashtagOrId = arguments.getString("hashtag_or_id");
  73. }
  74. View rootView = inflater.inflate(R.layout.fragment_timeline, container, false);
  75. // Setup the SwipeRefreshLayout.
  76. Context context = getContext();
  77. swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh_layout);
  78. swipeRefreshLayout.setOnRefreshListener(this);
  79. // Setup the RecyclerView.
  80. RecyclerView recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
  81. recyclerView.setHasFixedSize(true);
  82. layoutManager = new LinearLayoutManager(context);
  83. recyclerView.setLayoutManager(layoutManager);
  84. DividerItemDecoration divider = new DividerItemDecoration(
  85. context, layoutManager.getOrientation());
  86. Drawable drawable = ThemeUtils.getDrawable(context, R.attr.status_divider_drawable,
  87. R.drawable.status_divider_dark);
  88. divider.setDrawable(drawable);
  89. recyclerView.addItemDecoration(divider);
  90. scrollListener = new EndlessOnScrollListener(layoutManager) {
  91. @Override
  92. public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
  93. TimelineAdapter adapter = (TimelineAdapter) view.getAdapter();
  94. Status status = adapter.getItem(adapter.getItemCount() - 2);
  95. if (status != null) {
  96. sendFetchTimelineRequest(status.id, null);
  97. } else {
  98. sendFetchTimelineRequest();
  99. }
  100. }
  101. };
  102. recyclerView.addOnScrollListener(scrollListener);
  103. adapter = new TimelineAdapter(this);
  104. recyclerView.setAdapter(adapter);
  105. if (jumpToTopAllowed()) {
  106. TabLayout layout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
  107. onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
  108. @Override
  109. public void onTabSelected(TabLayout.Tab tab) {}
  110. @Override
  111. public void onTabUnselected(TabLayout.Tab tab) {}
  112. @Override
  113. public void onTabReselected(TabLayout.Tab tab) {
  114. jumpToTop();
  115. }
  116. };
  117. layout.addOnTabSelectedListener(onTabSelectedListener);
  118. }
  119. return rootView;
  120. }
  121. @Override
  122. public void onDestroy() {
  123. super.onDestroy();
  124. if (listCall != null) listCall.cancel();
  125. }
  126. @Override
  127. public void onDestroyView() {
  128. if (jumpToTopAllowed()) {
  129. TabLayout tabLayout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
  130. tabLayout.removeOnTabSelectedListener(onTabSelectedListener);
  131. }
  132. super.onDestroyView();
  133. }
  134. private boolean jumpToTopAllowed() {
  135. return kind != Kind.TAG && kind != Kind.FAVOURITES;
  136. }
  137. private void jumpToTop() {
  138. layoutManager.scrollToPosition(0);
  139. scrollListener.reset();
  140. }
  141. private void sendFetchTimelineRequest(@Nullable final String fromId, @Nullable String uptoId) {
  142. MastodonAPI api = ((BaseActivity) getActivity()).mastodonAPI;
  143. Callback<List<Status>> cb = new Callback<List<Status>>() {
  144. @Override
  145. public void onResponse(Call<List<Status>> call, retrofit2.Response<List<Status>> response) {
  146. if (response.isSuccessful()) {
  147. onFetchTimelineSuccess(response.body(), fromId);
  148. } else {
  149. onFetchTimelineFailure(new Exception(response.message()));
  150. }
  151. }
  152. @Override
  153. public void onFailure(Call<List<Status>> call, Throwable t) {
  154. onFetchTimelineFailure((Exception) t);
  155. }
  156. };
  157. switch (kind) {
  158. default:
  159. case HOME: {
  160. listCall = api.homeTimeline(fromId, uptoId, null);
  161. break;
  162. }
  163. case PUBLIC_FEDERATED: {
  164. listCall = api.publicTimeline(null, fromId, uptoId, null);
  165. break;
  166. }
  167. case PUBLIC_LOCAL: {
  168. listCall = api.publicTimeline(true, fromId, uptoId, null);
  169. break;
  170. }
  171. case TAG: {
  172. listCall = api.hashtagTimeline(hashtagOrId, null, fromId, uptoId, null);
  173. break;
  174. }
  175. case USER: {
  176. listCall = api.accountStatuses(hashtagOrId, fromId, uptoId, null);
  177. break;
  178. }
  179. case FAVOURITES: {
  180. listCall = api.favourites(fromId, uptoId, null);
  181. break;
  182. }
  183. }
  184. callList.add(listCall);
  185. listCall.enqueue(cb);
  186. }
  187. private void sendFetchTimelineRequest() {
  188. sendFetchTimelineRequest(null, null);
  189. }
  190. private static boolean findStatus(List<Status> statuses, String id) {
  191. for (Status status : statuses) {
  192. if (status.id.equals(id)) {
  193. return true;
  194. }
  195. }
  196. return false;
  197. }
  198. public void onFetchTimelineSuccess(List<Status> statuses, String fromId) {
  199. if (fromId != null) {
  200. if (statuses.size() > 0 && !findStatus(statuses, fromId)) {
  201. adapter.addItems(statuses);
  202. }
  203. } else {
  204. adapter.update(statuses);
  205. }
  206. swipeRefreshLayout.setRefreshing(false);
  207. }
  208. public void onFetchTimelineFailure(Exception exception) {
  209. swipeRefreshLayout.setRefreshing(false);
  210. Log.e(TAG, "Fetch Failure: " + exception.getMessage());
  211. }
  212. public void onRefresh() {
  213. Status status = adapter.getItem(0);
  214. if (status != null) {
  215. sendFetchTimelineRequest(null, status.id);
  216. } else {
  217. sendFetchTimelineRequest();
  218. }
  219. }
  220. public void onReply(int position) {
  221. super.reply(adapter.getItem(position));
  222. }
  223. public void onReblog(final boolean reblog, final int position) {
  224. super.reblog(adapter.getItem(position), reblog, adapter, position);
  225. }
  226. public void onFavourite(final boolean favourite, final int position) {
  227. super.favourite(adapter.getItem(position), favourite, adapter, position);
  228. }
  229. public void onMore(View view, final int position) {
  230. super.more(adapter.getItem(position), view, adapter, position);
  231. }
  232. public void onViewMedia(String url, Status.MediaAttachment.Type type) {
  233. super.viewMedia(url, type);
  234. }
  235. public void onViewThread(int position) {
  236. super.viewThread(adapter.getItem(position));
  237. }
  238. public void onViewTag(String tag) {
  239. if (kind == Kind.TAG && hashtagOrId.equals(tag)) {
  240. // If already viewing a tag page, then ignore any request to view that tag again.
  241. return;
  242. }
  243. super.viewTag(tag);
  244. }
  245. public void onViewAccount(String id) {
  246. if (kind == Kind.USER && hashtagOrId.equals(id)) {
  247. /* If already viewing an account page, then any requests to view that account page
  248. * should be ignored. */
  249. return;
  250. }
  251. super.viewAccount(id);
  252. }
  253. }