diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e46c5273..8f73899a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -95,6 +95,8 @@
+
+
diff --git a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt
new file mode 100644
index 00000000..da4aefba
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt
@@ -0,0 +1,199 @@
+package com.keylesspalace.tusky
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.support.v4.widget.TextViewCompat
+import android.support.v7.widget.DividerItemDecoration
+import android.support.v7.widget.LinearLayoutManager
+import android.support.v7.widget.RecyclerView
+import android.support.v7.widget.Toolbar
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.ProgressBar
+import android.widget.TextView
+import com.keylesspalace.tusky.entity.MastoList
+import com.keylesspalace.tusky.fragment.TimelineFragment
+import com.keylesspalace.tusky.network.MastodonApi
+import com.keylesspalace.tusky.util.ThemeUtils
+import com.mikepenz.google_material_typeface_library.GoogleMaterial
+import com.mikepenz.iconics.IconicsDrawable
+import com.varunest.sparkbutton.helpers.Utils
+import retrofit2.Call
+import retrofit2.Response
+import java.lang.ref.WeakReference
+
+/**
+ * Created by charlag on 1/4/18.
+ */
+
+interface ListsView {
+ fun update(state: State)
+ fun openTimeline(listId: String)
+}
+
+
+data class State(val lists: List, val isLoading: Boolean)
+
+class ListsViewModel(private val api: MastodonApi) {
+
+ private var _view: WeakReference? = null
+ private val view: ListsView? get() = _view?.get()
+ private var state = State(listOf(), false)
+
+ fun attach(view: ListsView) {
+ this._view = WeakReference(view)
+ updateView()
+ loadIfNeeded()
+ }
+
+ fun detach() {
+ this._view = null
+ }
+
+ fun didSelectItem(id: String) {
+ view?.openTimeline(id)
+ }
+
+ private fun loadIfNeeded() {
+ if (state.isLoading || !state.lists.isEmpty()) return
+ updateState(state.copy(isLoading = false))
+
+ api.getLists().enqueue(object : retrofit2.Callback> {
+ override fun onResponse(call: Call>, response: Response>) {
+ updateState(state.copy(lists = response.body() ?: listOf(), isLoading = false))
+ }
+
+ override fun onFailure(call: Call>, t: Throwable?) {
+ updateState(state.copy(isLoading = false))
+ }
+ })
+ }
+
+ private fun updateState(state: State) {
+ this.state = state
+ view?.update(state)
+ }
+
+ private fun updateView() {
+ view?.update(state)
+ }
+}
+
+class ListsActivity : BaseActivity(), ListsView {
+
+ companion object {
+ @JvmStatic
+ fun newIntent(context: Context): Intent {
+ return Intent(context, ListsActivity::class.java)
+ }
+ }
+
+ private lateinit var recyclerView: RecyclerView
+ private lateinit var progressBar: ProgressBar
+
+ private lateinit var viewModel: ListsViewModel
+ private val adapter = ListsAdapter()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_lists)
+
+ val toolbar = findViewById(R.id.toolbar)
+ recyclerView = findViewById(R.id.lists_recycler)
+ progressBar = findViewById(R.id.progress_bar)
+
+ setSupportActionBar(toolbar)
+ val bar = supportActionBar
+ if (bar != null) {
+ bar.title = getString(R.string.title_lists)
+ bar.setDisplayHomeAsUpEnabled(true)
+ bar.setDisplayShowHomeEnabled(true)
+ }
+
+ recyclerView.adapter = adapter
+ recyclerView.layoutManager = LinearLayoutManager(this)
+ recyclerView.addItemDecoration(
+ DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
+
+ viewModel = lastNonConfigurationInstance as? ListsViewModel ?: ListsViewModel(mastodonApi)
+ viewModel.attach(this)
+ }
+
+ override fun onDestroy() {
+ viewModel.detach()
+ super.onDestroy()
+ }
+
+ override fun onRetainCustomNonConfigurationInstance(): Any {
+ return viewModel
+ }
+
+
+ override fun update(state: State) {
+ adapter.update(state.lists)
+ progressBar.visibility = if (state.isLoading) View.VISIBLE else View.GONE
+
+ }
+
+ override fun openTimeline(listId: String) {
+ startActivity(
+ ModalTimelineActivity.newIntent(this, TimelineFragment.Kind.LIST, listId))
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId == android.R.id.home) {
+ onBackPressed()
+ return true
+ }
+ return false
+ }
+
+ private inner class ListsAdapter : RecyclerView.Adapter() {
+
+ private val items = mutableListOf()
+
+ fun update(list: List) {
+ this.items.clear()
+ this.items.addAll(list)
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int = items.size
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
+ return LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false)
+ .let(this::ListViewHolder)
+ .apply {
+ val context = nameTextView.context
+ val icon = IconicsDrawable(context, GoogleMaterial.Icon.gmd_list)
+ val size = Utils.dpToPx(context, 20)
+ ThemeUtils.setDrawableTint(context, icon, android.R.attr.textColorTertiary)
+ icon.setBounds(0, 0, size, size)
+ nameTextView.compoundDrawablePadding = Utils.dpToPx(context, 8)
+ TextViewCompat.setCompoundDrawablesRelative(
+ nameTextView, icon, null, null, null)
+ }
+ }
+
+ override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
+ holder.nameTextView.text = items[position].title
+ }
+
+ private inner class ListViewHolder(view: View) : RecyclerView.ViewHolder(view),
+ View.OnClickListener {
+ val nameTextView: TextView = view.findViewById(R.id.list_name_textview)
+
+ init {
+ view.setOnClickListener(this)
+ }
+
+ override fun onClick(v: View?) {
+ viewModel.didSelectItem(items[adapterPosition].id)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
index 6f46b09d..aed7abe9 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
@@ -77,6 +77,7 @@ public class MainActivity extends BaseActivity implements ActionButtonActivity {
private static final long DRAWER_ITEM_LOG_OUT = 7;
private static final long DRAWER_ITEM_FOLLOW_REQUESTS = 8;
private static final long DRAWER_ITEM_SAVED_TOOT = 9;
+ private static final long DRAWER_ITEM_LISTS = 10;
private static int COMPOSE_RESULT = 1;
@@ -311,6 +312,7 @@ public class MainActivity extends BaseActivity implements ActionButtonActivity {
List listItem = new ArrayList<>();
listItem.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_EDIT_PROFILE).withName(getString(R.string.action_edit_profile)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_person));
listItem.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_FAVOURITES).withName(getString(R.string.action_view_favourites)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_star));
+ listItem.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_LISTS).withName(R.string.action_lists).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_list));
listItem.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_MUTED_USERS).withName(getString(R.string.action_view_mutes)).withSelectable(false).withIcon(muteDrawable));
listItem.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_BLOCKED_USERS).withName(getString(R.string.action_view_blocks)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_block));
listItem.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_SEARCH).withName(getString(R.string.action_search)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_search));
@@ -366,6 +368,8 @@ public class MainActivity extends BaseActivity implements ActionButtonActivity {
} else if (drawerItemIdentifier == DRAWER_ITEM_SAVED_TOOT) {
Intent intent = new Intent(MainActivity.this, SavedTootActivity.class);
startActivity(intent);
+ } else if (drawerItemIdentifier == DRAWER_ITEM_LISTS) {
+ startActivity(ListsActivity.newIntent(this));
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt
new file mode 100644
index 00000000..fc81589a
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt
@@ -0,0 +1,63 @@
+package com.keylesspalace.tusky
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.support.design.widget.FloatingActionButton
+import android.support.v7.widget.Toolbar
+import android.view.MenuItem
+import android.widget.FrameLayout
+import com.keylesspalace.tusky.fragment.TimelineFragment
+import com.keylesspalace.tusky.interfaces.ActionButtonActivity
+
+class ModalTimelineActivity : BaseActivity(), ActionButtonActivity {
+ companion object {
+
+ private const val ARG_KIND = "kind"
+ private const val ARG_ARG = "arg"
+ @JvmStatic fun newIntent(context: Context, kind: TimelineFragment.Kind,
+ argument: String?): Intent {
+ val intent = Intent(context, ModalTimelineActivity::class.java)
+ intent.putExtra(ARG_KIND, kind)
+ intent.putExtra(ARG_ARG, argument)
+ return intent
+ }
+
+ }
+ lateinit var contentFrame: FrameLayout
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_modal_timeline)
+ contentFrame = findViewById(R.id.content_frame)
+
+ val toolbar = findViewById(R.id.toolbar)
+ setSupportActionBar(toolbar)
+ val bar = supportActionBar
+ if (bar != null) {
+ bar.title = getString(R.string.title_list_timeline)
+ bar.setDisplayHomeAsUpEnabled(true)
+ bar.setDisplayShowHomeEnabled(true)
+ }
+
+ if (supportFragmentManager.findFragmentById(R.id.content_frame) == null) {
+ val kind = intent?.getSerializableExtra(ARG_KIND) as? TimelineFragment.Kind ?:
+ TimelineFragment.Kind.HOME
+ val argument = intent?.getStringExtra(ARG_ARG)
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.content_frame, TimelineFragment.newInstance(kind, argument))
+ .commit()
+ }
+ }
+
+ override fun getActionButton(): FloatingActionButton? = null
+
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId == android.R.id.home) {
+ onBackPressed()
+ return true
+ }
+ return false
+ }
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/MastoList.kt b/app/src/main/java/com/keylesspalace/tusky/entity/MastoList.kt
new file mode 100644
index 00000000..60b2bbc3
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/MastoList.kt
@@ -0,0 +1,7 @@
+package com.keylesspalace.tusky.entity
+
+/**
+ * Created by charlag on 1/4/18.
+ */
+
+data class MastoList(val id: String, val title: String)
\ No newline at end of file
diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
index 452120a9..5276023f 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
@@ -80,7 +80,8 @@ public class TimelineFragment extends SFragment implements
PUBLIC_FEDERATED,
TAG,
USER,
- FAVOURITES
+ FAVOURITES,
+ LIST
}
private enum FetchEnd {
@@ -158,7 +159,7 @@ public class TimelineFragment extends SFragment implements
Bundle savedInstanceState) {
Bundle arguments = getArguments();
kind = Kind.valueOf(arguments.getString(KIND_ARG));
- if (kind == Kind.TAG || kind == Kind.USER) {
+ if (kind == Kind.TAG || kind == Kind.USER || kind == Kind.LIST) {
hashtagOrId = arguments.getString(HASHTAG_OR_ID_ARG);
}
@@ -209,19 +210,23 @@ public class TimelineFragment extends SFragment implements
if (jumpToTopAllowed()) {
TabLayout layout = getActivity().findViewById(R.id.tab_layout);
- onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
- @Override
- public void onTabSelected(TabLayout.Tab tab) {}
+ if (layout != null) {
+ onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
+ @Override
+ public void onTabSelected(TabLayout.Tab tab) {
+ }
- @Override
- public void onTabUnselected(TabLayout.Tab tab) {}
+ @Override
+ public void onTabUnselected(TabLayout.Tab tab) {
+ }
- @Override
- public void onTabReselected(TabLayout.Tab tab) {
- jumpToTop();
- }
- };
- layout.addOnTabSelectedListener(onTabSelectedListener);
+ @Override
+ public void onTabReselected(TabLayout.Tab tab) {
+ jumpToTop();
+ }
+ };
+ layout.addOnTabSelectedListener(onTabSelectedListener);
+ }
}
/* This is delayed until onActivityCreated solely because MainActivity.composeButton isn't
@@ -273,7 +278,9 @@ public class TimelineFragment extends SFragment implements
public void onDestroyView() {
if (jumpToTopAllowed()) {
TabLayout tabLayout = getActivity().findViewById(R.id.tab_layout);
- tabLayout.removeOnTabSelectedListener(onTabSelectedListener);
+ if (tabLayout != null) {
+ tabLayout.removeOnTabSelectedListener(onTabSelectedListener);
+ }
}
LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(timelineReceiver);
super.onDestroyView();
@@ -532,6 +539,8 @@ public class TimelineFragment extends SFragment implements
return api.accountStatuses(tagOrId, fromId, uptoId, LOAD_AT_ONCE, null);
case FAVOURITES:
return api.favourites(fromId, uptoId, LOAD_AT_ONCE);
+ case LIST:
+ return api.listTimeline(tagOrId, fromId, uptoId, LOAD_AT_ONCE);
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.java b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.java
index c37bb4eb..18ca3093 100644
--- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.java
+++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.java
@@ -22,6 +22,7 @@ import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.AppCredentials;
import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Card;
+import com.keylesspalace.tusky.entity.MastoList;
import com.keylesspalace.tusky.entity.Notification;
import com.keylesspalace.tusky.entity.Profile;
import com.keylesspalace.tusky.entity.Relationship;
@@ -67,6 +68,12 @@ public interface MastodonApi {
@Query("max_id") String maxId,
@Query("since_id") String sinceId,
@Query("limit") Integer limit);
+ @GET("api/v1/timelines/list/{listId}")
+ Call> listTimeline(
+ @Path("listId") String listId,
+ @Query("max_id") String maxId,
+ @Query("since_id") String sinceId,
+ @Query("limit") Integer limit);
@GET("api/v1/notifications")
Call> notifications(
@@ -236,4 +243,7 @@ public interface MastodonApi {
Call statusCard(
@Path("id") String statusId
);
+
+ @GET("/api/v1/lists")
+ Call> getLists();
}
diff --git a/app/src/main/res/layout/activity_lists.xml b/app/src/main/res/layout/activity_lists.xml
new file mode 100644
index 00000000..4eaccefc
--- /dev/null
+++ b/app/src/main/res/layout/activity_lists.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_modal_timeline.xml b/app/src/main/res/layout/activity_modal_timeline.xml
new file mode 100644
index 00000000..997a39a1
--- /dev/null
+++ b/app/src/main/res/layout/activity_modal_timeline.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_list.xml b/app/src/main/res/layout/item_list.xml
new file mode 100644
index 00000000..b1ea23ba
--- /dev/null
+++ b/app/src/main/res/layout/item_list.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/toolbar_basic.xml b/app/src/main/res/layout/toolbar_basic.xml
index 1086ee28..fd09a276 100644
--- a/app/src/main/res/layout/toolbar_basic.xml
+++ b/app/src/main/res/layout/toolbar_basic.xml
@@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
-
-
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b2c20f6d..75f0ff41 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -272,6 +272,9 @@
Media
Replying to @%s
load more
+ Lists
+ Lists
+ List timeline