123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633 |
- /* Copyright 2021 Tusky Contributors
- *
- * This file is a part of Tusky.
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 3 of the
- * License, or (at your option) any later version.
- *
- * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
- * Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with Tusky; if not,
- * see <http://www.gnu.org/licenses>. */
- package com.keylesspalace.tusky.components.timeline
- import android.os.Bundle
- import android.util.Log
- import android.view.LayoutInflater
- import android.view.Menu
- import android.view.MenuInflater
- import android.view.MenuItem
- import android.view.View
- import android.view.ViewGroup
- import android.view.accessibility.AccessibilityManager
- import androidx.core.content.ContextCompat
- import androidx.core.view.MenuProvider
- import androidx.lifecycle.Lifecycle
- import androidx.lifecycle.ViewModelProvider
- import androidx.lifecycle.lifecycleScope
- import androidx.paging.LoadState
- import androidx.preference.PreferenceManager
- import androidx.recyclerview.widget.DividerItemDecoration
- import androidx.recyclerview.widget.LinearLayoutManager
- import androidx.recyclerview.widget.RecyclerView
- import androidx.recyclerview.widget.SimpleItemAnimator
- import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
- import at.connyduck.sparkbutton.helpers.Utils
- import autodispose2.androidx.lifecycle.autoDispose
- import com.google.android.material.color.MaterialColors
- import com.keylesspalace.tusky.BaseActivity
- import com.keylesspalace.tusky.R
- import com.keylesspalace.tusky.adapter.StatusBaseViewHolder
- import com.keylesspalace.tusky.appstore.EventHub
- import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
- import com.keylesspalace.tusky.appstore.StatusComposedEvent
- import com.keylesspalace.tusky.components.accountlist.AccountListActivity
- import com.keylesspalace.tusky.components.accountlist.AccountListActivity.Companion.newIntent
- import com.keylesspalace.tusky.components.preference.PreferencesFragment.ReadingOrder
- import com.keylesspalace.tusky.components.timeline.viewmodel.CachedTimelineViewModel
- import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineViewModel
- import com.keylesspalace.tusky.components.timeline.viewmodel.TimelineViewModel
- import com.keylesspalace.tusky.databinding.FragmentTimelineBinding
- import com.keylesspalace.tusky.di.Injectable
- import com.keylesspalace.tusky.di.ViewModelFactory
- import com.keylesspalace.tusky.entity.Status
- import com.keylesspalace.tusky.fragment.SFragment
- import com.keylesspalace.tusky.interfaces.ActionButtonActivity
- import com.keylesspalace.tusky.interfaces.RefreshableFragment
- import com.keylesspalace.tusky.interfaces.ReselectableFragment
- import com.keylesspalace.tusky.interfaces.StatusActionListener
- import com.keylesspalace.tusky.settings.PrefKeys
- import com.keylesspalace.tusky.util.CardViewMode
- import com.keylesspalace.tusky.util.ListStatusAccessibilityDelegate
- import com.keylesspalace.tusky.util.StatusDisplayOptions
- import com.keylesspalace.tusky.util.hide
- import com.keylesspalace.tusky.util.show
- import com.keylesspalace.tusky.util.unsafeLazy
- import com.keylesspalace.tusky.util.viewBinding
- import com.keylesspalace.tusky.viewdata.AttachmentViewData
- import com.keylesspalace.tusky.viewdata.StatusViewData
- import com.mikepenz.iconics.IconicsDrawable
- import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
- import com.mikepenz.iconics.utils.colorInt
- import com.mikepenz.iconics.utils.sizeDp
- import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
- import io.reactivex.rxjava3.core.Observable
- import kotlinx.coroutines.flow.collectLatest
- import kotlinx.coroutines.launch
- import java.io.IOException
- import java.util.concurrent.TimeUnit
- import javax.inject.Inject
- class TimelineFragment :
- SFragment(),
- OnRefreshListener,
- StatusActionListener,
- Injectable,
- ReselectableFragment,
- RefreshableFragment,
- MenuProvider {
- @Inject
- lateinit var viewModelFactory: ViewModelFactory
- @Inject
- lateinit var eventHub: EventHub
- private val viewModel: TimelineViewModel by unsafeLazy {
- if (kind == TimelineViewModel.Kind.HOME) {
- ViewModelProvider(this, viewModelFactory)[CachedTimelineViewModel::class.java]
- } else {
- ViewModelProvider(this, viewModelFactory)[NetworkTimelineViewModel::class.java]
- }
- }
- private val binding by viewBinding(FragmentTimelineBinding::bind)
- private lateinit var kind: TimelineViewModel.Kind
- private lateinit var adapter: TimelinePagingAdapter
- private var isSwipeToRefreshEnabled = true
- private var hideFab = false
- /**
- * Adapter position of the placeholder that was most recently clicked to "Load more". If null
- * then there is no active "Load more" operation
- */
- private var loadMorePosition: Int? = null
- /** ID of the status immediately below the most recent "Load more" placeholder click */
- // The Paging library assumes that the user will be scrolling down a list of items,
- // and if new items are loaded but not visible then it's reasonable to scroll to the top
- // of the inserted items. It does not seem to be possible to disable that behaviour.
- //
- // That behaviour should depend on the user's preferred reading order. If they prefer to
- // read oldest first then the list should be scrolled to the bottom of the freshly
- // inserted statuses.
- //
- // To do this:
- //
- // 1. When "Load more" is clicked (onLoadMore()):
- // a. Remember the adapter position of the "Load more" item in loadMorePosition
- // b. Remember the ID of the status immediately below the "Load more" item in
- // statusIdBelowLoadMore
- // 2. After the new items have been inserted, search the adapter for the position of the
- // status with id == statusIdBelowLoadMore.
- // 3. If this position is still visible on screen then do nothing, otherwise, scroll the view
- // so that the status is visible.
- //
- // The user can then scroll up to read the new statuses.
- private var statusIdBelowLoadMore: String? = null
- /** The user's preferred reading order */
- private lateinit var readingOrder: ReadingOrder
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val arguments = requireArguments()
- kind = TimelineViewModel.Kind.valueOf(arguments.getString(KIND_ARG)!!)
- val id: String? = if (kind == TimelineViewModel.Kind.USER ||
- kind == TimelineViewModel.Kind.USER_PINNED ||
- kind == TimelineViewModel.Kind.USER_WITH_REPLIES ||
- kind == TimelineViewModel.Kind.LIST
- ) {
- arguments.getString(ID_ARG)!!
- } else {
- null
- }
- val tags = if (kind == TimelineViewModel.Kind.TAG) {
- arguments.getStringArrayList(HASHTAGS_ARG)!!
- } else {
- listOf()
- }
- viewModel.init(
- kind,
- id,
- tags,
- )
- isSwipeToRefreshEnabled = arguments.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, true)
- val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
- readingOrder = ReadingOrder.from(preferences.getString(PrefKeys.READING_ORDER, null))
- val statusDisplayOptions = StatusDisplayOptions(
- animateAvatars = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),
- mediaPreviewEnabled = accountManager.activeAccount!!.mediaPreviewEnabled,
- useAbsoluteTime = preferences.getBoolean(PrefKeys.ABSOLUTE_TIME_VIEW, false),
- showBotOverlay = preferences.getBoolean(PrefKeys.SHOW_BOT_OVERLAY, true),
- useBlurhash = preferences.getBoolean(PrefKeys.USE_BLURHASH, true),
- cardViewMode = if (preferences.getBoolean(
- PrefKeys.SHOW_CARDS_IN_TIMELINES,
- false
- )
- ) CardViewMode.INDENTED else CardViewMode.NONE,
- confirmReblogs = preferences.getBoolean(PrefKeys.CONFIRM_REBLOGS, true),
- confirmFavourites = preferences.getBoolean(PrefKeys.CONFIRM_FAVOURITES, false),
- hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false),
- animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false),
- showSensitiveMedia = accountManager.activeAccount!!.alwaysShowSensitiveMedia,
- openSpoiler = accountManager.activeAccount!!.alwaysOpenSpoiler
- )
- adapter = TimelinePagingAdapter(
- statusDisplayOptions,
- this
- )
- }
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.fragment_timeline, container, false)
- }
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
- setupSwipeRefreshLayout()
- setupRecyclerView()
- adapter.addLoadStateListener { loadState ->
- if (loadState.refresh != LoadState.Loading && loadState.source.refresh != LoadState.Loading) {
- binding.swipeRefreshLayout.isRefreshing = false
- }
- binding.statusView.hide()
- binding.progressBar.hide()
- if (adapter.itemCount == 0) {
- when (loadState.refresh) {
- is LoadState.NotLoading -> {
- if (loadState.append is LoadState.NotLoading && loadState.source.refresh is LoadState.NotLoading) {
- binding.statusView.show()
- binding.statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty)
- }
- }
- is LoadState.Error -> {
- binding.statusView.show()
- if ((loadState.refresh as LoadState.Error).error is IOException) {
- binding.statusView.setup(R.drawable.elephant_offline, R.string.error_network)
- } else {
- binding.statusView.setup(R.drawable.elephant_error, R.string.error_generic)
- }
- }
- is LoadState.Loading -> {
- binding.progressBar.show()
- }
- }
- }
- }
- adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
- override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
- if (positionStart == 0 && adapter.itemCount != itemCount) {
- binding.recyclerView.post {
- if (getView() != null) {
- if (isSwipeToRefreshEnabled) {
- binding.recyclerView.scrollBy(0, Utils.dpToPx(requireContext(), -30))
- } else binding.recyclerView.scrollToPosition(0)
- }
- }
- }
- if (readingOrder == ReadingOrder.OLDEST_FIRST) {
- updateReadingPositionForOldestFirst()
- }
- }
- })
- viewLifecycleOwner.lifecycleScope.launch {
- viewModel.statuses.collectLatest { pagingData ->
- adapter.submitData(pagingData)
- }
- }
- if (actionButtonPresent()) {
- val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
- hideFab = preferences.getBoolean("fabHide", false)
- binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
- override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
- val composeButton = (activity as ActionButtonActivity).actionButton
- if (composeButton != null) {
- if (hideFab) {
- if (dy > 0 && composeButton.isShown) {
- composeButton.hide() // hides the button if we're scrolling down
- } else if (dy < 0 && !composeButton.isShown) {
- composeButton.show() // shows it if we are scrolling up
- }
- } else if (!composeButton.isShown) {
- composeButton.show()
- }
- }
- }
- })
- }
- eventHub.events
- .observeOn(AndroidSchedulers.mainThread())
- .autoDispose(this, Lifecycle.Event.ON_DESTROY)
- .subscribe { event ->
- when (event) {
- is PreferenceChangedEvent -> {
- onPreferenceChanged(event.preferenceKey)
- }
- is StatusComposedEvent -> {
- val status = event.status
- handleStatusComposeEvent(status)
- }
- }
- }
- }
- override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
- if (isSwipeToRefreshEnabled) {
- menuInflater.inflate(R.menu.fragment_timeline, menu)
- menu.findItem(R.id.action_refresh)?.apply {
- icon = IconicsDrawable(requireContext(), GoogleMaterial.Icon.gmd_refresh).apply {
- sizeDp = 20
- colorInt =
- MaterialColors.getColor(binding.root, android.R.attr.textColorPrimary)
- }
- }
- }
- }
- override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
- return when (menuItem.itemId) {
- R.id.action_refresh -> {
- if (isSwipeToRefreshEnabled) {
- binding.swipeRefreshLayout.isRefreshing = true
- refreshContent()
- true
- } else {
- false
- }
- }
- else -> false
- }
- }
- /**
- * Set the correct reading position in the timeline after the user clicked "Load more",
- * assuming the reading position should be below the freshly-loaded statuses.
- */
- // Note: The positionStart parameter to onItemRangeInserted() does not always
- // match the adapter position where data was inserted (which is why loadMorePosition
- // is tracked manually, see this bug report for another example:
- // https://github.com/android/architecture-components-samples/issues/726).
- private fun updateReadingPositionForOldestFirst() {
- var position = loadMorePosition ?: return
- val statusIdBelowLoadMore = statusIdBelowLoadMore ?: return
- var status: StatusViewData?
- while (adapter.peek(position).let { status = it; it != null }) {
- if (status?.id == statusIdBelowLoadMore) {
- val lastVisiblePosition =
- (binding.recyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
- if (position > lastVisiblePosition) {
- binding.recyclerView.scrollToPosition(position)
- }
- break
- }
- position++
- }
- loadMorePosition = null
- }
- private fun setupSwipeRefreshLayout() {
- binding.swipeRefreshLayout.isEnabled = isSwipeToRefreshEnabled
- binding.swipeRefreshLayout.setOnRefreshListener(this)
- binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
- }
- private fun setupRecyclerView() {
- binding.recyclerView.setAccessibilityDelegateCompat(
- ListStatusAccessibilityDelegate(binding.recyclerView, this) { pos ->
- if (pos in 0 until adapter.itemCount) {
- adapter.peek(pos)
- } else {
- null
- }
- }
- )
- binding.recyclerView.setHasFixedSize(true)
- binding.recyclerView.layoutManager = LinearLayoutManager(context)
- val divider = DividerItemDecoration(context, RecyclerView.VERTICAL)
- binding.recyclerView.addItemDecoration(divider)
- // CWs are expanded without animation, buttons animate itself, we don't need it basically
- (binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
- binding.recyclerView.adapter = adapter
- }
- override fun onRefresh() {
- binding.statusView.hide()
- adapter.refresh()
- }
- override fun onReply(position: Int) {
- val status = adapter.peek(position)?.asStatusOrNull() ?: return
- super.reply(status.status)
- }
- override fun onReblog(reblog: Boolean, position: Int) {
- val status = adapter.peek(position)?.asStatusOrNull() ?: return
- viewModel.reblog(reblog, status)
- }
- override fun onFavourite(favourite: Boolean, position: Int) {
- val status = adapter.peek(position)?.asStatusOrNull() ?: return
- viewModel.favorite(favourite, status)
- }
- override fun onBookmark(bookmark: Boolean, position: Int) {
- val status = adapter.peek(position)?.asStatusOrNull() ?: return
- viewModel.bookmark(bookmark, status)
- }
- override fun onVoteInPoll(position: Int, choices: List<Int>) {
- val status = adapter.peek(position)?.asStatusOrNull() ?: return
- viewModel.voteInPoll(choices, status)
- }
- override fun onMore(view: View, position: Int) {
- val status = adapter.peek(position)?.asStatusOrNull() ?: return
- super.more(status.status, view, position)
- }
- override fun onOpenReblog(position: Int) {
- val status = adapter.peek(position)?.asStatusOrNull() ?: return
- super.openReblog(status.status)
- }
- override fun onExpandedChange(expanded: Boolean, position: Int) {
- val status = adapter.peek(position)?.asStatusOrNull() ?: return
- viewModel.changeExpanded(expanded, status)
- }
- override fun onContentHiddenChange(isShowing: Boolean, position: Int) {
- val status = adapter.peek(position)?.asStatusOrNull() ?: return
- viewModel.changeContentShowing(isShowing, status)
- }
- override fun onShowReblogs(position: Int) {
- val statusId = adapter.peek(position)?.asStatusOrNull()?.id ?: return
- val intent = newIntent(requireContext(), AccountListActivity.Type.REBLOGGED, statusId)
- (activity as BaseActivity).startActivityWithSlideInAnimation(intent)
- }
- override fun onShowFavs(position: Int) {
- val statusId = adapter.peek(position)?.asStatusOrNull()?.id ?: return
- val intent = newIntent(requireContext(), AccountListActivity.Type.FAVOURITED, statusId)
- (activity as BaseActivity).startActivityWithSlideInAnimation(intent)
- }
- override fun onLoadMore(position: Int) {
- val placeholder = adapter.peek(position)?.asPlaceholderOrNull() ?: return
- loadMorePosition = position
- statusIdBelowLoadMore = adapter.peek(position + 1)?.id
- viewModel.loadMore(placeholder.id)
- }
- override fun onContentCollapsedChange(isCollapsed: Boolean, position: Int) {
- val status = adapter.peek(position)?.asStatusOrNull() ?: return
- viewModel.changeContentCollapsed(isCollapsed, status)
- }
- override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) {
- val status = adapter.peek(position)?.asStatusOrNull() ?: return
- super.viewMedia(
- attachmentIndex,
- AttachmentViewData.list(status.actionable),
- view
- )
- }
- override fun onViewThread(position: Int) {
- val status = adapter.peek(position)?.asStatusOrNull() ?: return
- super.viewThread(status.actionable.id, status.actionable.url)
- }
- override fun onViewTag(tag: String) {
- if (viewModel.kind == TimelineViewModel.Kind.TAG && viewModel.tags.size == 1 &&
- viewModel.tags.contains(tag)
- ) {
- // If already viewing a tag page, then ignore any request to view that tag again.
- return
- }
- super.viewTag(tag)
- }
- override fun onViewAccount(id: String) {
- if ((
- viewModel.kind == TimelineViewModel.Kind.USER ||
- viewModel.kind == TimelineViewModel.Kind.USER_WITH_REPLIES
- ) &&
- viewModel.id == id
- ) {
- /* If already viewing an account page, then any requests to view that account page
- * should be ignored. */
- return
- }
- super.viewAccount(id)
- }
- private fun onPreferenceChanged(key: String) {
- val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
- when (key) {
- PrefKeys.FAB_HIDE -> {
- hideFab = sharedPreferences.getBoolean(PrefKeys.FAB_HIDE, false)
- }
- PrefKeys.MEDIA_PREVIEW_ENABLED -> {
- val enabled = accountManager.activeAccount!!.mediaPreviewEnabled
- val oldMediaPreviewEnabled = adapter.mediaPreviewEnabled
- if (enabled != oldMediaPreviewEnabled) {
- adapter.mediaPreviewEnabled = enabled
- adapter.notifyItemRangeChanged(0, adapter.itemCount)
- }
- }
- PrefKeys.READING_ORDER -> {
- readingOrder = ReadingOrder.from(
- sharedPreferences.getString(PrefKeys.READING_ORDER, null)
- )
- }
- }
- }
- private fun handleStatusComposeEvent(status: Status) {
- when (kind) {
- TimelineViewModel.Kind.HOME,
- TimelineViewModel.Kind.PUBLIC_FEDERATED,
- TimelineViewModel.Kind.PUBLIC_LOCAL -> adapter.refresh()
- TimelineViewModel.Kind.USER,
- TimelineViewModel.Kind.USER_WITH_REPLIES -> if (status.account.id == viewModel.id) {
- adapter.refresh()
- }
- TimelineViewModel.Kind.TAG,
- TimelineViewModel.Kind.FAVOURITES,
- TimelineViewModel.Kind.LIST,
- TimelineViewModel.Kind.BOOKMARKS,
- TimelineViewModel.Kind.USER_PINNED -> return
- }
- }
- public override fun removeItem(position: Int) {
- val status = adapter.peek(position)?.asStatusOrNull() ?: return
- viewModel.removeStatusWithId(status.id)
- }
- private fun actionButtonPresent(): Boolean {
- return viewModel.kind != TimelineViewModel.Kind.TAG &&
- viewModel.kind != TimelineViewModel.Kind.FAVOURITES &&
- viewModel.kind != TimelineViewModel.Kind.BOOKMARKS &&
- activity is ActionButtonActivity
- }
- private var talkBackWasEnabled = false
- override fun onResume() {
- super.onResume()
- val a11yManager =
- ContextCompat.getSystemService(requireContext(), AccessibilityManager::class.java)
- val wasEnabled = talkBackWasEnabled
- talkBackWasEnabled = a11yManager?.isEnabled == true
- Log.d(TAG, "talkback was enabled: $wasEnabled, now $talkBackWasEnabled")
- if (talkBackWasEnabled && !wasEnabled) {
- adapter.notifyItemRangeChanged(0, adapter.itemCount)
- }
- startUpdateTimestamp()
- }
- /**
- * Start to update adapter every minute to refresh timestamp
- * If setting absoluteTimeView is false
- * Auto dispose observable on pause
- */
- private fun startUpdateTimestamp() {
- val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
- val useAbsoluteTime = preferences.getBoolean(PrefKeys.ABSOLUTE_TIME_VIEW, false)
- if (!useAbsoluteTime) {
- Observable.interval(0, 1, TimeUnit.MINUTES)
- .observeOn(AndroidSchedulers.mainThread())
- .autoDispose(this, Lifecycle.Event.ON_PAUSE)
- .subscribe {
- adapter.notifyItemRangeChanged(0, adapter.itemCount, listOf(StatusBaseViewHolder.Key.KEY_CREATED))
- }
- }
- }
- override fun onReselect() {
- if (isAdded) {
- binding.recyclerView.layoutManager?.scrollToPosition(0)
- binding.recyclerView.stopScroll()
- }
- }
- override fun refreshContent() {
- onRefresh()
- }
- companion object {
- private const val TAG = "TimelineF" // logging tag
- private const val KIND_ARG = "kind"
- private const val ID_ARG = "id"
- private const val HASHTAGS_ARG = "hashtags"
- private const val ARG_ENABLE_SWIPE_TO_REFRESH = "enableSwipeToRefresh"
- fun newInstance(
- kind: TimelineViewModel.Kind,
- hashtagOrId: String? = null,
- enableSwipeToRefresh: Boolean = true
- ): TimelineFragment {
- val fragment = TimelineFragment()
- val arguments = Bundle(3)
- arguments.putString(KIND_ARG, kind.name)
- arguments.putString(ID_ARG, hashtagOrId)
- arguments.putBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, enableSwipeToRefresh)
- fragment.arguments = arguments
- return fragment
- }
- @JvmStatic
- fun newHashtagInstance(hashtags: List<String>): TimelineFragment {
- val fragment = TimelineFragment()
- val arguments = Bundle(3)
- arguments.putString(KIND_ARG, TimelineViewModel.Kind.TAG.name)
- arguments.putStringArrayList(HASHTAGS_ARG, ArrayList(hashtags))
- arguments.putBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, true)
- fragment.arguments = arguments
- return fragment
- }
- }
- }
|