migrating to ViewBinding part 4: Fragments (#2108)
* migrating to ViewBinding part 4: Fragment * fix imports * don't use viewBinding extension in ViewImage and ViewVideoFragment * don't use viewBinding extension in ViewImage and ViewVideoFragment
This commit is contained in:
parent
fbb0b11d83
commit
bea5098cc1
18 changed files with 412 additions and 349 deletions
|
@ -125,6 +125,7 @@ dependencies {
|
||||||
implementation "androidx.emoji:emoji-appcompat:1.1.0"
|
implementation "androidx.emoji:emoji-appcompat:1.1.0"
|
||||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
|
||||||
|
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
|
||||||
implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycleVersion"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
||||||
implementation "androidx.paging:paging-runtime-ktx:2.1.2"
|
implementation "androidx.paging:paging-runtime-ktx:2.1.2"
|
||||||
|
|
|
@ -23,11 +23,13 @@ import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import com.keylesspalace.tusky.databinding.FragmentAccountsInListBinding
|
||||||
|
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
import com.keylesspalace.tusky.entity.Account
|
import com.keylesspalace.tusky.entity.Account
|
||||||
|
@ -38,9 +40,6 @@ import com.keylesspalace.tusky.viewmodel.State
|
||||||
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
||||||
import com.uber.autodispose.autoDispose
|
import com.uber.autodispose.autoDispose
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.fragment_accounts_in_list.*
|
|
||||||
import kotlinx.android.synthetic.main.item_follow_request.*
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -48,23 +47,11 @@ private typealias AccountInfo = Pair<Account, Boolean>
|
||||||
|
|
||||||
class AccountsInListFragment : DialogFragment(), Injectable {
|
class AccountsInListFragment : DialogFragment(), Injectable {
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val LIST_ID_ARG = "listId"
|
|
||||||
private const val LIST_NAME_ARG = "listName"
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun newInstance(listId: String, listName: String): AccountsInListFragment {
|
|
||||||
val args = Bundle().apply {
|
|
||||||
putString(LIST_ID_ARG, listId)
|
|
||||||
putString(LIST_NAME_ARG, listName)
|
|
||||||
}
|
|
||||||
return AccountsInListFragment().apply { arguments = args }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var viewModelFactory: ViewModelFactory
|
lateinit var viewModelFactory: ViewModelFactory
|
||||||
lateinit var viewModel: AccountsInListViewModel
|
|
||||||
|
private val viewModel: AccountsInListViewModel by viewModels { viewModelFactory }
|
||||||
|
private val binding by viewBinding(FragmentAccountsInListBinding::bind)
|
||||||
|
|
||||||
private lateinit var listId: String
|
private lateinit var listId: String
|
||||||
private lateinit var listName: String
|
private lateinit var listName: String
|
||||||
|
@ -79,7 +66,6 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setStyle(STYLE_NORMAL, R.style.TuskyDialogFragmentStyle)
|
setStyle(STYLE_NORMAL, R.style.TuskyDialogFragmentStyle)
|
||||||
viewModel = viewModelFactory.create(AccountsInListViewModel::class.java)
|
|
||||||
val args = requireArguments()
|
val args = requireArguments()
|
||||||
listId = args.getString(LIST_ID_ARG)!!
|
listId = args.getString(LIST_ID_ARG)!!
|
||||||
listName = args.getString(LIST_NAME_ARG)!!
|
listName = args.getString(LIST_NAME_ARG)!!
|
||||||
|
@ -100,12 +86,11 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
binding.accountsRecycler.layoutManager = LinearLayoutManager(view.context)
|
||||||
accountsRecycler.layoutManager = LinearLayoutManager(view.context)
|
binding.accountsRecycler.adapter = adapter
|
||||||
accountsRecycler.adapter = adapter
|
|
||||||
|
|
||||||
accountsSearchRecycler.layoutManager = LinearLayoutManager(view.context)
|
binding.accountsSearchRecycler.layoutManager = LinearLayoutManager(view.context)
|
||||||
accountsSearchRecycler.adapter = searchAdapter
|
binding.accountsSearchRecycler.adapter = searchAdapter
|
||||||
|
|
||||||
viewModel.state
|
viewModel.state
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
@ -114,15 +99,15 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
||||||
adapter.submitList(state.accounts.asRightOrNull() ?: listOf())
|
adapter.submitList(state.accounts.asRightOrNull() ?: listOf())
|
||||||
|
|
||||||
when (state.accounts) {
|
when (state.accounts) {
|
||||||
is Either.Right -> messageView.hide()
|
is Either.Right -> binding.messageView.hide()
|
||||||
is Either.Left -> handleError(state.accounts.value)
|
is Either.Left -> handleError(state.accounts.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSearchView(state)
|
setupSearchView(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
searchView.isSubmitButtonEnabled = true
|
binding.searchView.isSubmitButtonEnabled = true
|
||||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||||
viewModel.search(query ?: "")
|
viewModel.search(query ?: "")
|
||||||
return true
|
return true
|
||||||
|
@ -141,30 +126,30 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
||||||
private fun setupSearchView(state: State) {
|
private fun setupSearchView(state: State) {
|
||||||
if (state.searchResult == null) {
|
if (state.searchResult == null) {
|
||||||
searchAdapter.submitList(listOf())
|
searchAdapter.submitList(listOf())
|
||||||
accountsSearchRecycler.hide()
|
binding.accountsSearchRecycler.hide()
|
||||||
accountsRecycler.show()
|
binding.accountsRecycler.show()
|
||||||
} else {
|
} else {
|
||||||
val listAccounts = state.accounts.asRightOrNull() ?: listOf()
|
val listAccounts = state.accounts.asRightOrNull() ?: listOf()
|
||||||
val newList = state.searchResult.map { acc ->
|
val newList = state.searchResult.map { acc ->
|
||||||
acc to listAccounts.contains(acc)
|
acc to listAccounts.contains(acc)
|
||||||
}
|
}
|
||||||
searchAdapter.submitList(newList)
|
searchAdapter.submitList(newList)
|
||||||
accountsSearchRecycler.show()
|
binding.accountsSearchRecycler.show()
|
||||||
accountsRecycler.hide()
|
binding.accountsRecycler.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleError(error: Throwable) {
|
private fun handleError(error: Throwable) {
|
||||||
messageView.show()
|
binding.messageView.show()
|
||||||
val retryAction = { _: View ->
|
val retryAction = { _: View ->
|
||||||
messageView.hide()
|
binding.messageView.hide()
|
||||||
viewModel.load(listId)
|
viewModel.load(listId)
|
||||||
}
|
}
|
||||||
if (error is IOException) {
|
if (error is IOException) {
|
||||||
messageView.setup(R.drawable.elephant_offline,
|
binding.messageView.setup(R.drawable.elephant_offline,
|
||||||
R.string.error_network, retryAction)
|
R.string.error_network, retryAction)
|
||||||
} else {
|
} else {
|
||||||
messageView.setup(R.drawable.elephant_error,
|
binding.messageView.setup(R.drawable.elephant_error,
|
||||||
R.string.error_generic, retryAction)
|
R.string.error_generic, retryAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,39 +172,28 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class Adapter : ListAdapter<Account, Adapter.ViewHolder>(AccountDiffer) {
|
inner class Adapter : ListAdapter<Account, BindingHolder<ItemFollowRequestBinding>>(AccountDiffer) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemFollowRequestBinding> {
|
||||||
val view = LayoutInflater.from(parent.context)
|
val binding = ItemFollowRequestBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
.inflate(R.layout.item_follow_request, parent, false)
|
val holder = BindingHolder(binding)
|
||||||
return ViewHolder(view)
|
|
||||||
|
binding.notificationTextView.hide()
|
||||||
|
binding.acceptButton.hide()
|
||||||
|
binding.rejectButton.setOnClickListener {
|
||||||
|
onRemoveFromList(getItem(holder.adapterPosition).id)
|
||||||
|
}
|
||||||
|
binding.rejectButton.contentDescription =
|
||||||
|
binding.root.context.getString(R.string.action_remove_from_list)
|
||||||
|
|
||||||
|
return holder
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: BindingHolder<ItemFollowRequestBinding>, position: Int) {
|
||||||
holder.bind(getItem(position))
|
val account = getItem(position)
|
||||||
}
|
holder.binding.displayNameTextView.text = account.name.emojify(account.emojis, holder.binding.displayNameTextView, animateEmojis)
|
||||||
|
holder.binding.usernameTextView.text = account.username
|
||||||
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
|
loadAvatar(account.avatar, holder.binding.avatar, radius, animateAvatar)
|
||||||
View.OnClickListener, LayoutContainer {
|
|
||||||
|
|
||||||
override val containerView = itemView
|
|
||||||
|
|
||||||
init {
|
|
||||||
acceptButton.hide()
|
|
||||||
rejectButton.setOnClickListener(this)
|
|
||||||
rejectButton.contentDescription =
|
|
||||||
itemView.context.getString(R.string.action_remove_from_list)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bind(account: Account) {
|
|
||||||
displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView, animateEmojis)
|
|
||||||
usernameTextView.text = account.username
|
|
||||||
loadAvatar(account.avatar, avatar, radius, animateAvatar)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(v: View?) {
|
|
||||||
onRemoveFromList(getItem(adapterPosition).id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,57 +206,58 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
||||||
return oldItem.second == newItem.second
|
return oldItem.second == newItem.second
|
||||||
&& oldItem.first.deepEquals(newItem.first)
|
&& oldItem.first.deepEquals(newItem.first)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class SearchAdapter : ListAdapter<AccountInfo, SearchAdapter.ViewHolder>(SearchDiffer) {
|
inner class SearchAdapter : ListAdapter<AccountInfo, BindingHolder<ItemFollowRequestBinding>>(SearchDiffer) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemFollowRequestBinding> {
|
||||||
val view = LayoutInflater.from(parent.context)
|
val binding = ItemFollowRequestBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
.inflate(R.layout.item_follow_request, parent, false)
|
val holder = BindingHolder(binding)
|
||||||
return ViewHolder(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
binding.notificationTextView.hide()
|
||||||
val (account, inAList) = getItem(position)
|
binding.acceptButton.hide()
|
||||||
holder.bind(account, inAList)
|
binding.rejectButton.setOnClickListener {
|
||||||
|
val (account, inAList) = getItem(holder.adapterPosition)
|
||||||
}
|
|
||||||
|
|
||||||
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
|
|
||||||
View.OnClickListener, LayoutContainer {
|
|
||||||
|
|
||||||
override val containerView = itemView
|
|
||||||
|
|
||||||
fun bind(account: Account, inAList: Boolean) {
|
|
||||||
displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView, animateEmojis)
|
|
||||||
usernameTextView.text = account.username
|
|
||||||
loadAvatar(account.avatar, avatar, radius, animateAvatar)
|
|
||||||
|
|
||||||
rejectButton.apply {
|
|
||||||
contentDescription = if (inAList) {
|
|
||||||
setImageResource(R.drawable.ic_reject_24dp)
|
|
||||||
getString(R.string.action_remove_from_list)
|
|
||||||
} else {
|
|
||||||
setImageResource(R.drawable.ic_plus_24dp)
|
|
||||||
getString(R.string.action_add_to_list)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
acceptButton.hide()
|
|
||||||
rejectButton.setOnClickListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(v: View?) {
|
|
||||||
val (account, inAList) = getItem(adapterPosition)
|
|
||||||
if (inAList) {
|
if (inAList) {
|
||||||
onRemoveFromList(account.id)
|
onRemoveFromList(account.id)
|
||||||
} else {
|
} else {
|
||||||
onAddToList(account)
|
onAddToList(account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return holder
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: BindingHolder<ItemFollowRequestBinding>, position: Int) {
|
||||||
|
val (account, inAList) = getItem(position)
|
||||||
|
|
||||||
|
holder.binding.displayNameTextView.text = account.name.emojify(account.emojis, holder.binding.displayNameTextView, animateEmojis)
|
||||||
|
holder.binding.usernameTextView.text = account.username
|
||||||
|
loadAvatar(account.avatar, holder.binding.avatar, radius, animateAvatar)
|
||||||
|
|
||||||
|
holder.binding.rejectButton.apply {
|
||||||
|
contentDescription = if (inAList) {
|
||||||
|
setImageResource(R.drawable.ic_reject_24dp)
|
||||||
|
getString(R.string.action_remove_from_list)
|
||||||
|
} else {
|
||||||
|
setImageResource(R.drawable.ic_plus_24dp)
|
||||||
|
getString(R.string.action_add_to_list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val LIST_ID_ARG = "listId"
|
||||||
|
private const val LIST_NAME_ARG = "listName"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun newInstance(listId: String, listName: String): AccountsInListFragment {
|
||||||
|
val args = Bundle().apply {
|
||||||
|
putString(LIST_ID_ARG, listId)
|
||||||
|
putString(LIST_NAME_ARG, listName)
|
||||||
|
}
|
||||||
|
return AccountsInListFragment().apply { arguments = args }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -69,6 +69,9 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
||||||
|
|
||||||
private val binding by viewBinding(ActivityViewMediaBinding::inflate)
|
private val binding by viewBinding(ActivityViewMediaBinding::inflate)
|
||||||
|
|
||||||
|
val toolbar: View
|
||||||
|
get() = binding.toolbar
|
||||||
|
|
||||||
var isToolbarVisible = true
|
var isToolbarVisible = true
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator
|
||||||
import com.keylesspalace.tusky.AccountActivity
|
import com.keylesspalace.tusky.AccountActivity
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.ViewTagActivity
|
import com.keylesspalace.tusky.ViewTagActivity
|
||||||
|
import com.keylesspalace.tusky.databinding.FragmentTimelineBinding
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
import com.keylesspalace.tusky.fragment.SFragment
|
import com.keylesspalace.tusky.fragment.SFragment
|
||||||
|
@ -38,7 +39,7 @@ import com.keylesspalace.tusky.util.CardViewMode
|
||||||
import com.keylesspalace.tusky.util.NetworkState
|
import com.keylesspalace.tusky.util.NetworkState
|
||||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
import kotlinx.android.synthetic.main.fragment_timeline.*
|
import com.keylesspalace.tusky.util.viewBinding
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ConversationsFragment : SFragment(), StatusActionListener, Injectable, ReselectableFragment {
|
class ConversationsFragment : SFragment(), StatusActionListener, Injectable, ReselectableFragment {
|
||||||
|
@ -48,6 +49,8 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
||||||
|
|
||||||
private val viewModel: ConversationsViewModel by viewModels { viewModelFactory }
|
private val viewModel: ConversationsViewModel by viewModels { viewModelFactory }
|
||||||
|
|
||||||
|
private val binding by viewBinding(FragmentTimelineBinding::bind)
|
||||||
|
|
||||||
private lateinit var adapter: ConversationAdapter
|
private lateinit var adapter: ConversationAdapter
|
||||||
|
|
||||||
private var layoutManager: LinearLayoutManager? = null
|
private var layoutManager: LinearLayoutManager? = null
|
||||||
|
@ -73,14 +76,14 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
||||||
|
|
||||||
adapter = ConversationAdapter(statusDisplayOptions, this, ::onTopLoaded, viewModel::retry)
|
adapter = ConversationAdapter(statusDisplayOptions, this, ::onTopLoaded, viewModel::retry)
|
||||||
|
|
||||||
recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
binding.recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
||||||
layoutManager = LinearLayoutManager(view.context)
|
layoutManager = LinearLayoutManager(view.context)
|
||||||
recyclerView.layoutManager = layoutManager
|
binding.recyclerView.layoutManager = layoutManager
|
||||||
recyclerView.adapter = adapter
|
binding.recyclerView.adapter = adapter
|
||||||
(recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
(binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||||
|
|
||||||
progressBar.hide()
|
binding.progressBar.hide()
|
||||||
statusView.hide()
|
binding.statusView.hide()
|
||||||
|
|
||||||
initSwipeToRefresh()
|
initSwipeToRefresh()
|
||||||
|
|
||||||
|
@ -97,16 +100,16 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
||||||
|
|
||||||
private fun initSwipeToRefresh() {
|
private fun initSwipeToRefresh() {
|
||||||
viewModel.refreshState.observe(viewLifecycleOwner) {
|
viewModel.refreshState.observe(viewLifecycleOwner) {
|
||||||
swipeRefreshLayout.isRefreshing = it == NetworkState.LOADING
|
binding.swipeRefreshLayout.isRefreshing = it == NetworkState.LOADING
|
||||||
}
|
}
|
||||||
swipeRefreshLayout.setOnRefreshListener {
|
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||||
viewModel.refresh()
|
viewModel.refresh()
|
||||||
}
|
}
|
||||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onTopLoaded() {
|
private fun onTopLoaded() {
|
||||||
recyclerView.scrollToPosition(0)
|
binding.recyclerView.scrollToPosition(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReblog(reblog: Boolean, position: Int) {
|
override fun onReblog(reblog: Boolean, position: Int) {
|
||||||
|
@ -183,7 +186,7 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
||||||
private fun jumpToTop() {
|
private fun jumpToTop() {
|
||||||
if (isAdded) {
|
if (isAdded) {
|
||||||
layoutManager?.scrollToPosition(0)
|
layoutManager?.scrollToPosition(0)
|
||||||
recyclerView.stopScroll()
|
binding.recyclerView.stopScroll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,7 @@ package com.keylesspalace.tusky.components.instancemute.fragment
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
@ -14,16 +12,17 @@ import com.google.android.material.snackbar.Snackbar
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.components.instancemute.adapter.DomainMutesAdapter
|
import com.keylesspalace.tusky.components.instancemute.adapter.DomainMutesAdapter
|
||||||
import com.keylesspalace.tusky.components.instancemute.interfaces.InstanceActionListener
|
import com.keylesspalace.tusky.components.instancemute.interfaces.InstanceActionListener
|
||||||
|
import com.keylesspalace.tusky.databinding.FragmentInstanceListBinding
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import com.keylesspalace.tusky.util.HttpHeaderLink
|
import com.keylesspalace.tusky.util.HttpHeaderLink
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
import com.keylesspalace.tusky.util.show
|
import com.keylesspalace.tusky.util.show
|
||||||
|
import com.keylesspalace.tusky.util.viewBinding
|
||||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener
|
import com.keylesspalace.tusky.view.EndlessOnScrollListener
|
||||||
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
||||||
import com.uber.autodispose.autoDispose
|
import com.uber.autodispose.autoDispose
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import kotlinx.android.synthetic.main.fragment_instance_list.*
|
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
@ -31,9 +30,12 @@ import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectable, InstanceActionListener {
|
class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectable, InstanceActionListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var api: MastodonApi
|
lateinit var api: MastodonApi
|
||||||
|
|
||||||
|
private val binding by viewBinding(FragmentInstanceListBinding::bind)
|
||||||
|
|
||||||
private var fetching = false
|
private var fetching = false
|
||||||
private var bottomId: String? = null
|
private var bottomId: String? = null
|
||||||
private var adapter = DomainMutesAdapter(this)
|
private var adapter = DomainMutesAdapter(this)
|
||||||
|
@ -42,12 +44,12 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
recyclerView.setHasFixedSize(true)
|
binding.recyclerView.setHasFixedSize(true)
|
||||||
recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
binding.recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
||||||
recyclerView.adapter = adapter
|
binding.recyclerView.adapter = adapter
|
||||||
|
|
||||||
val layoutManager = LinearLayoutManager(view.context)
|
val layoutManager = LinearLayoutManager(view.context)
|
||||||
recyclerView.layoutManager = layoutManager
|
binding.recyclerView.layoutManager = layoutManager
|
||||||
|
|
||||||
scrollListener = object : EndlessOnScrollListener(layoutManager) {
|
scrollListener = object : EndlessOnScrollListener(layoutManager) {
|
||||||
override fun onLoadMore(totalItemsCount: Int, view: RecyclerView) {
|
override fun onLoadMore(totalItemsCount: Int, view: RecyclerView) {
|
||||||
|
@ -57,7 +59,7 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recyclerView.addOnScrollListener(scrollListener)
|
binding.recyclerView.addOnScrollListener(scrollListener)
|
||||||
fetchInstances()
|
fetchInstances()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +87,7 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
||||||
override fun onResponse(call: Call<Any>, response: Response<Any>) {
|
override fun onResponse(call: Call<Any>, response: Response<Any>) {
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
adapter.removeItem(position)
|
adapter.removeItem(position)
|
||||||
Snackbar.make(recyclerView, getString(R.string.confirmation_domain_unmuted, instance), Snackbar.LENGTH_LONG)
|
Snackbar.make(binding.recyclerView, getString(R.string.confirmation_domain_unmuted, instance), Snackbar.LENGTH_LONG)
|
||||||
.setAction(R.string.action_undo) {
|
.setAction(R.string.action_undo) {
|
||||||
mute(true, instance, position)
|
mute(true, instance, position)
|
||||||
}
|
}
|
||||||
|
@ -103,10 +105,10 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fetching = true
|
fetching = true
|
||||||
instanceProgressBar.show()
|
binding.instanceProgressBar.show()
|
||||||
|
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
recyclerView.post { adapter.bottomLoading = true }
|
binding.recyclerView.post { adapter.bottomLoading = true }
|
||||||
}
|
}
|
||||||
|
|
||||||
api.domainBlocks(id, bottomId)
|
api.domainBlocks(id, bottomId)
|
||||||
|
@ -116,7 +118,7 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
||||||
val instances = response.body()
|
val instances = response.body()
|
||||||
|
|
||||||
if (response.isSuccessful && instances != null) {
|
if (response.isSuccessful && instances != null) {
|
||||||
onFetchInstancesSuccess(instances, response.headers().get("Link"))
|
onFetchInstancesSuccess(instances, response.headers()["Link"])
|
||||||
} else {
|
} else {
|
||||||
onFetchInstancesFailure(Exception(response.message()))
|
onFetchInstancesFailure(Exception(response.message()))
|
||||||
}
|
}
|
||||||
|
@ -127,7 +129,7 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
||||||
|
|
||||||
private fun onFetchInstancesSuccess(instances: List<String>, linkHeader: String?) {
|
private fun onFetchInstancesSuccess(instances: List<String>, linkHeader: String?) {
|
||||||
adapter.bottomLoading = false
|
adapter.bottomLoading = false
|
||||||
instanceProgressBar.hide()
|
binding.instanceProgressBar.hide()
|
||||||
|
|
||||||
val links = HttpHeaderLink.parse(linkHeader)
|
val links = HttpHeaderLink.parse(linkHeader)
|
||||||
val next = HttpHeaderLink.findByRelationType(links, "next")
|
val next = HttpHeaderLink.findByRelationType(links, "next")
|
||||||
|
@ -137,32 +139,32 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
||||||
fetching = false
|
fetching = false
|
||||||
|
|
||||||
if (adapter.itemCount == 0) {
|
if (adapter.itemCount == 0) {
|
||||||
messageView.show()
|
binding.messageView.show()
|
||||||
messageView.setup(
|
binding.messageView.setup(
|
||||||
R.drawable.elephant_friend_empty,
|
R.drawable.elephant_friend_empty,
|
||||||
R.string.message_empty,
|
R.string.message_empty,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
messageView.hide()
|
binding.messageView.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onFetchInstancesFailure(throwable: Throwable) {
|
private fun onFetchInstancesFailure(throwable: Throwable) {
|
||||||
fetching = false
|
fetching = false
|
||||||
instanceProgressBar.hide()
|
binding.instanceProgressBar.hide()
|
||||||
Log.e(TAG, "Fetch failure", throwable)
|
Log.e(TAG, "Fetch failure", throwable)
|
||||||
|
|
||||||
if (adapter.itemCount == 0) {
|
if (adapter.itemCount == 0) {
|
||||||
messageView.show()
|
binding.messageView.show()
|
||||||
if (throwable is IOException) {
|
if (throwable is IOException) {
|
||||||
messageView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
binding.messageView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||||
messageView.hide()
|
binding.messageView.hide()
|
||||||
this.fetchInstances(null)
|
this.fetchInstances(null)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
binding.messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||||
messageView.hide()
|
binding.messageView.hide()
|
||||||
this.fetchInstances(null)
|
this.fetchInstances(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,13 @@ import androidx.fragment.app.activityViewModels
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.components.report.ReportViewModel
|
import com.keylesspalace.tusky.components.report.ReportViewModel
|
||||||
import com.keylesspalace.tusky.components.report.Screen
|
import com.keylesspalace.tusky.components.report.Screen
|
||||||
|
import com.keylesspalace.tusky.databinding.FragmentReportDoneBinding
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
import com.keylesspalace.tusky.util.Loading
|
import com.keylesspalace.tusky.util.Loading
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
import com.keylesspalace.tusky.util.show
|
import com.keylesspalace.tusky.util.show
|
||||||
import kotlinx.android.synthetic.main.fragment_report_done.*
|
import com.keylesspalace.tusky.util.viewBinding
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
||||||
|
@ -37,8 +38,10 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
||||||
|
|
||||||
private val viewModel: ReportViewModel by activityViewModels { viewModelFactory }
|
private val viewModel: ReportViewModel by activityViewModels { viewModelFactory }
|
||||||
|
|
||||||
|
private val binding by viewBinding(FragmentReportDoneBinding::bind)
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
textReported.text = getString(R.string.report_sent_success, viewModel.accountUserName)
|
binding.textReported.text = getString(R.string.report_sent_success, viewModel.accountUserName)
|
||||||
handleClicks()
|
handleClicks()
|
||||||
subscribeObservables()
|
subscribeObservables()
|
||||||
}
|
}
|
||||||
|
@ -46,14 +49,14 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
||||||
private fun subscribeObservables() {
|
private fun subscribeObservables() {
|
||||||
viewModel.muteState.observe(viewLifecycleOwner) {
|
viewModel.muteState.observe(viewLifecycleOwner) {
|
||||||
if (it !is Loading) {
|
if (it !is Loading) {
|
||||||
buttonMute.show()
|
binding.buttonMute.show()
|
||||||
progressMute.show()
|
binding.progressMute.show()
|
||||||
} else {
|
} else {
|
||||||
buttonMute.hide()
|
binding.buttonMute.hide()
|
||||||
progressMute.hide()
|
binding.progressMute.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonMute.setText(when (it.data) {
|
binding.buttonMute.setText(when (it.data) {
|
||||||
true -> R.string.action_unmute
|
true -> R.string.action_unmute
|
||||||
else -> R.string.action_mute
|
else -> R.string.action_mute
|
||||||
})
|
})
|
||||||
|
@ -61,14 +64,14 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
||||||
|
|
||||||
viewModel.blockState.observe(viewLifecycleOwner) {
|
viewModel.blockState.observe(viewLifecycleOwner) {
|
||||||
if (it !is Loading) {
|
if (it !is Loading) {
|
||||||
buttonBlock.show()
|
binding.buttonBlock.show()
|
||||||
progressBlock.show()
|
binding.progressBlock.show()
|
||||||
}
|
}
|
||||||
else{
|
else {
|
||||||
buttonBlock.hide()
|
binding.buttonBlock.hide()
|
||||||
progressBlock.hide()
|
binding.progressBlock.hide()
|
||||||
}
|
}
|
||||||
buttonBlock.setText(when (it.data) {
|
binding.buttonBlock.setText(when (it.data) {
|
||||||
true -> R.string.action_unblock
|
true -> R.string.action_unblock
|
||||||
else -> R.string.action_block
|
else -> R.string.action_block
|
||||||
})
|
})
|
||||||
|
@ -77,13 +80,13 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleClicks() {
|
private fun handleClicks() {
|
||||||
buttonDone.setOnClickListener {
|
binding.buttonDone.setOnClickListener {
|
||||||
viewModel.navigateTo(Screen.Finish)
|
viewModel.navigateTo(Screen.Finish)
|
||||||
}
|
}
|
||||||
buttonBlock.setOnClickListener {
|
binding.buttonBlock.setOnClickListener {
|
||||||
viewModel.toggleBlock()
|
viewModel.toggleBlock()
|
||||||
}
|
}
|
||||||
buttonMute.setOnClickListener {
|
binding.buttonMute.setOnClickListener {
|
||||||
viewModel.toggleMute()
|
viewModel.toggleMute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,5 +94,4 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance() = ReportDoneFragment()
|
fun newInstance() = ReportDoneFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,10 +24,10 @@ import com.google.android.material.snackbar.Snackbar
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.components.report.ReportViewModel
|
import com.keylesspalace.tusky.components.report.ReportViewModel
|
||||||
import com.keylesspalace.tusky.components.report.Screen
|
import com.keylesspalace.tusky.components.report.Screen
|
||||||
|
import com.keylesspalace.tusky.databinding.FragmentReportNoteBinding
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
import com.keylesspalace.tusky.util.*
|
import com.keylesspalace.tusky.util.*
|
||||||
import kotlinx.android.synthetic.main.fragment_report_note.*
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -38,6 +38,8 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
||||||
|
|
||||||
private val viewModel: ReportViewModel by activityViewModels { viewModelFactory }
|
private val viewModel: ReportViewModel by activityViewModels { viewModelFactory }
|
||||||
|
|
||||||
|
private val binding by viewBinding(FragmentReportNoteBinding::bind)
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
fillViews()
|
fillViews()
|
||||||
handleChanges()
|
handleChanges()
|
||||||
|
@ -46,29 +48,29 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChanges() {
|
private fun handleChanges() {
|
||||||
editNote.doAfterTextChanged {
|
binding.editNote.doAfterTextChanged {
|
||||||
viewModel.reportNote = it?.toString() ?: ""
|
viewModel.reportNote = it?.toString() ?: ""
|
||||||
}
|
}
|
||||||
checkIsNotifyRemote.setOnCheckedChangeListener { _, isChecked ->
|
binding.checkIsNotifyRemote.setOnCheckedChangeListener { _, isChecked ->
|
||||||
viewModel.isRemoteNotify = isChecked
|
viewModel.isRemoteNotify = isChecked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fillViews() {
|
private fun fillViews() {
|
||||||
editNote.setText(viewModel.reportNote)
|
binding.editNote.setText(viewModel.reportNote)
|
||||||
|
|
||||||
if (viewModel.isRemoteAccount){
|
if (viewModel.isRemoteAccount){
|
||||||
checkIsNotifyRemote.show()
|
binding.checkIsNotifyRemote.show()
|
||||||
reportDescriptionRemoteInstance.show()
|
binding.reportDescriptionRemoteInstance.show()
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
checkIsNotifyRemote.hide()
|
binding.checkIsNotifyRemote.hide()
|
||||||
reportDescriptionRemoteInstance.hide()
|
binding.reportDescriptionRemoteInstance.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewModel.isRemoteAccount)
|
if (viewModel.isRemoteAccount)
|
||||||
checkIsNotifyRemote.text = getString(R.string.report_remote_instance, viewModel.remoteServer)
|
binding.checkIsNotifyRemote.text = getString(R.string.report_remote_instance, viewModel.remoteServer)
|
||||||
checkIsNotifyRemote.isChecked = viewModel.isRemoteNotify
|
binding.checkIsNotifyRemote.isChecked = viewModel.isRemoteNotify
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun subscribeObservables() {
|
private fun subscribeObservables() {
|
||||||
|
@ -83,13 +85,13 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showError(error: Throwable?) {
|
private fun showError(error: Throwable?) {
|
||||||
editNote.isEnabled = true
|
binding.editNote.isEnabled = true
|
||||||
checkIsNotifyRemote.isEnabled = true
|
binding.checkIsNotifyRemote.isEnabled = true
|
||||||
buttonReport.isEnabled = true
|
binding.buttonReport.isEnabled = true
|
||||||
buttonBack.isEnabled = true
|
binding.buttonBack.isEnabled = true
|
||||||
progressBar.hide()
|
binding.progressBar.hide()
|
||||||
|
|
||||||
Snackbar.make(buttonBack, if (error is IOException) R.string.error_network else R.string.error_generic, Snackbar.LENGTH_LONG)
|
Snackbar.make(binding.buttonBack, if (error is IOException) R.string.error_network else R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||||
.apply {
|
.apply {
|
||||||
setAction(R.string.action_retry) {
|
setAction(R.string.action_retry) {
|
||||||
sendReport()
|
sendReport()
|
||||||
|
@ -103,19 +105,19 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showLoading() {
|
private fun showLoading() {
|
||||||
buttonReport.isEnabled = false
|
binding.buttonReport.isEnabled = false
|
||||||
buttonBack.isEnabled = false
|
binding.buttonBack.isEnabled = false
|
||||||
editNote.isEnabled = false
|
binding.editNote.isEnabled = false
|
||||||
checkIsNotifyRemote.isEnabled = false
|
binding.checkIsNotifyRemote.isEnabled = false
|
||||||
progressBar.show()
|
binding.progressBar.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleClicks() {
|
private fun handleClicks() {
|
||||||
buttonBack.setOnClickListener {
|
binding.buttonBack.setOnClickListener {
|
||||||
viewModel.navigateTo(Screen.Back)
|
viewModel.navigateTo(Screen.Back)
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonReport.setOnClickListener {
|
binding.buttonReport.setOnClickListener {
|
||||||
sendReport()
|
sendReport()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,5 +125,4 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance() = ReportNoteFragment()
|
fun newInstance() = ReportNoteFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ import com.keylesspalace.tusky.components.report.ReportViewModel
|
||||||
import com.keylesspalace.tusky.components.report.Screen
|
import com.keylesspalace.tusky.components.report.Screen
|
||||||
import com.keylesspalace.tusky.components.report.adapter.AdapterHandler
|
import com.keylesspalace.tusky.components.report.adapter.AdapterHandler
|
||||||
import com.keylesspalace.tusky.components.report.adapter.StatusesAdapter
|
import com.keylesspalace.tusky.components.report.adapter.StatusesAdapter
|
||||||
|
import com.keylesspalace.tusky.databinding.FragmentReportStatusesBinding
|
||||||
import com.keylesspalace.tusky.db.AccountManager
|
import com.keylesspalace.tusky.db.AccountManager
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
|
@ -44,8 +45,8 @@ import com.keylesspalace.tusky.util.CardViewMode
|
||||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
import com.keylesspalace.tusky.util.show
|
import com.keylesspalace.tusky.util.show
|
||||||
|
import com.keylesspalace.tusky.util.viewBinding
|
||||||
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
||||||
import kotlinx.android.synthetic.main.fragment_report_statuses.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Injectable, AdapterHandler {
|
class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Injectable, AdapterHandler {
|
||||||
|
@ -58,6 +59,8 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
||||||
|
|
||||||
private val viewModel: ReportViewModel by activityViewModels { viewModelFactory }
|
private val viewModel: ReportViewModel by activityViewModels { viewModelFactory }
|
||||||
|
|
||||||
|
private val binding by viewBinding(FragmentReportStatusesBinding::bind)
|
||||||
|
|
||||||
private lateinit var adapter: StatusesAdapter
|
private lateinit var adapter: StatusesAdapter
|
||||||
|
|
||||||
private var snackbarErrorRetry: Snackbar? = null
|
private var snackbarErrorRetry: Snackbar? = null
|
||||||
|
@ -93,9 +96,9 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSwipeRefreshLayout() {
|
private fun setupSwipeRefreshLayout() {
|
||||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||||
|
|
||||||
swipeRefreshLayout.setOnRefreshListener {
|
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||||
snackbarErrorRetry?.dismiss()
|
snackbarErrorRetry?.dismiss()
|
||||||
viewModel.refreshStatuses()
|
viewModel.refreshStatuses()
|
||||||
}
|
}
|
||||||
|
@ -118,10 +121,10 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
||||||
adapter = StatusesAdapter(statusDisplayOptions,
|
adapter = StatusesAdapter(statusDisplayOptions,
|
||||||
viewModel.statusViewState, this)
|
viewModel.statusViewState, this)
|
||||||
|
|
||||||
recyclerView.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
|
binding.recyclerView.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
|
||||||
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||||
recyclerView.adapter = adapter
|
binding.recyclerView.adapter = adapter
|
||||||
(recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
(binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||||
|
|
||||||
viewModel.statuses.observe(viewLifecycleOwner) {
|
viewModel.statuses.observe(viewLifecycleOwner) {
|
||||||
adapter.submitList(it)
|
adapter.submitList(it)
|
||||||
|
@ -129,9 +132,9 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
||||||
|
|
||||||
viewModel.networkStateAfter.observe(viewLifecycleOwner) {
|
viewModel.networkStateAfter.observe(viewLifecycleOwner) {
|
||||||
if (it?.status == com.keylesspalace.tusky.util.Status.RUNNING)
|
if (it?.status == com.keylesspalace.tusky.util.Status.RUNNING)
|
||||||
progressBarBottom.show()
|
binding.progressBarBottom.show()
|
||||||
else
|
else
|
||||||
progressBarBottom.hide()
|
binding.progressBarBottom.hide()
|
||||||
|
|
||||||
if (it?.status == com.keylesspalace.tusky.util.Status.FAILED)
|
if (it?.status == com.keylesspalace.tusky.util.Status.FAILED)
|
||||||
showError(it.msg)
|
showError(it.msg)
|
||||||
|
@ -139,22 +142,22 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
||||||
|
|
||||||
viewModel.networkStateBefore.observe(viewLifecycleOwner) {
|
viewModel.networkStateBefore.observe(viewLifecycleOwner) {
|
||||||
if (it?.status == com.keylesspalace.tusky.util.Status.RUNNING)
|
if (it?.status == com.keylesspalace.tusky.util.Status.RUNNING)
|
||||||
progressBarTop.show()
|
binding.progressBarTop.show()
|
||||||
else
|
else
|
||||||
progressBarTop.hide()
|
binding.progressBarTop.hide()
|
||||||
|
|
||||||
if (it?.status == com.keylesspalace.tusky.util.Status.FAILED)
|
if (it?.status == com.keylesspalace.tusky.util.Status.FAILED)
|
||||||
showError(it.msg)
|
showError(it.msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.networkStateRefresh.observe(viewLifecycleOwner) {
|
viewModel.networkStateRefresh.observe(viewLifecycleOwner) {
|
||||||
if (it?.status == com.keylesspalace.tusky.util.Status.RUNNING && !swipeRefreshLayout.isRefreshing)
|
if (it?.status == com.keylesspalace.tusky.util.Status.RUNNING && !binding.swipeRefreshLayout.isRefreshing)
|
||||||
progressBarLoading.show()
|
binding.progressBarLoading.show()
|
||||||
else
|
else
|
||||||
progressBarLoading.hide()
|
binding.progressBarLoading.hide()
|
||||||
|
|
||||||
if (it?.status != com.keylesspalace.tusky.util.Status.RUNNING)
|
if (it?.status != com.keylesspalace.tusky.util.Status.RUNNING)
|
||||||
swipeRefreshLayout.isRefreshing = false
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
if (it?.status == com.keylesspalace.tusky.util.Status.FAILED)
|
if (it?.status == com.keylesspalace.tusky.util.Status.FAILED)
|
||||||
showError(it.msg)
|
showError(it.msg)
|
||||||
}
|
}
|
||||||
|
@ -162,7 +165,7 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
||||||
|
|
||||||
private fun showError(@Suppress("UNUSED_PARAMETER") msg: String?) {
|
private fun showError(@Suppress("UNUSED_PARAMETER") msg: String?) {
|
||||||
if (snackbarErrorRetry?.isShown != true) {
|
if (snackbarErrorRetry?.isShown != true) {
|
||||||
snackbarErrorRetry = Snackbar.make(swipeRefreshLayout, R.string.failed_fetch_statuses, Snackbar.LENGTH_INDEFINITE)
|
snackbarErrorRetry = Snackbar.make(binding.swipeRefreshLayout, R.string.failed_fetch_statuses, Snackbar.LENGTH_INDEFINITE)
|
||||||
snackbarErrorRetry?.setAction(R.string.action_retry) {
|
snackbarErrorRetry?.setAction(R.string.action_retry) {
|
||||||
viewModel.retryStatusLoad()
|
viewModel.retryStatusLoad()
|
||||||
}
|
}
|
||||||
|
@ -172,11 +175,11 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
||||||
|
|
||||||
|
|
||||||
private fun handleClicks() {
|
private fun handleClicks() {
|
||||||
buttonCancel.setOnClickListener {
|
binding.buttonCancel.setOnClickListener {
|
||||||
viewModel.navigateTo(Screen.Back)
|
viewModel.navigateTo(Screen.Back)
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonContinue.setOnClickListener {
|
binding.buttonContinue.setOnClickListener {
|
||||||
viewModel.navigateTo(Screen.Note)
|
viewModel.navigateTo(Screen.Note)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,10 @@ import com.keylesspalace.tusky.components.search.adapter.SearchAccountsAdapter
|
||||||
import com.keylesspalace.tusky.entity.Account
|
import com.keylesspalace.tusky.entity.Account
|
||||||
import com.keylesspalace.tusky.settings.PrefKeys
|
import com.keylesspalace.tusky.settings.PrefKeys
|
||||||
import com.keylesspalace.tusky.util.NetworkState
|
import com.keylesspalace.tusky.util.NetworkState
|
||||||
import kotlinx.android.synthetic.main.fragment_search.*
|
|
||||||
|
|
||||||
class SearchAccountsFragment : SearchFragment<Account>() {
|
class SearchAccountsFragment : SearchFragment<Account>() {
|
||||||
override fun createAdapter(): PagedListAdapter<Account, *> {
|
override fun createAdapter(): PagedListAdapter<Account, *> {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(searchRecyclerView.context)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(binding.searchRecyclerView.context)
|
||||||
|
|
||||||
return SearchAccountsAdapter(
|
return SearchAccountsAdapter(
|
||||||
this,
|
this,
|
||||||
|
@ -46,5 +45,4 @@ class SearchAccountsFragment : SearchFragment<Account>() {
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance() = SearchAccountsFragment()
|
fun newInstance() = SearchAccountsFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,11 @@ import com.keylesspalace.tusky.BottomSheetActivity
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.ViewTagActivity
|
import com.keylesspalace.tusky.ViewTagActivity
|
||||||
import com.keylesspalace.tusky.components.search.SearchViewModel
|
import com.keylesspalace.tusky.components.search.SearchViewModel
|
||||||
|
import com.keylesspalace.tusky.databinding.FragmentSearchBinding
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||||
import com.keylesspalace.tusky.util.*
|
import com.keylesspalace.tusky.util.*
|
||||||
import kotlinx.android.synthetic.main.fragment_search.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
||||||
|
@ -32,6 +32,8 @@ abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
||||||
|
|
||||||
protected val viewModel: SearchViewModel by activityViewModels { viewModelFactory }
|
protected val viewModel: SearchViewModel by activityViewModels { viewModelFactory }
|
||||||
|
|
||||||
|
protected val binding by viewBinding(FragmentSearchBinding::bind)
|
||||||
|
|
||||||
private var snackbarErrorRetry: Snackbar? = null
|
private var snackbarErrorRetry: Snackbar? = null
|
||||||
|
|
||||||
abstract fun createAdapter(): PagedListAdapter<T, *>
|
abstract fun createAdapter(): PagedListAdapter<T, *>
|
||||||
|
@ -48,8 +50,8 @@ abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSwipeRefreshLayout() {
|
private fun setupSwipeRefreshLayout() {
|
||||||
swipeRefreshLayout.setOnRefreshListener(this)
|
binding.swipeRefreshLayout.setOnRefreshListener(this)
|
||||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun subscribeObservables() {
|
private fun subscribeObservables() {
|
||||||
|
@ -59,7 +61,7 @@ abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
||||||
|
|
||||||
networkStateRefresh.observe(viewLifecycleOwner) {
|
networkStateRefresh.observe(viewLifecycleOwner) {
|
||||||
|
|
||||||
searchProgressBar.visible(it == NetworkState.LOADING)
|
binding.searchProgressBar.visible(it == NetworkState.LOADING)
|
||||||
|
|
||||||
if (it.status == Status.FAILED) {
|
if (it.status == Status.FAILED) {
|
||||||
showError()
|
showError()
|
||||||
|
@ -69,7 +71,7 @@ abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
||||||
|
|
||||||
networkState.observe(viewLifecycleOwner) {
|
networkState.observe(viewLifecycleOwner) {
|
||||||
|
|
||||||
progressBarBottom.visible(it == NetworkState.LOADING)
|
binding.progressBarBottom.visible(it == NetworkState.LOADING)
|
||||||
|
|
||||||
if (it.status == Status.FAILED) {
|
if (it.status == Status.FAILED) {
|
||||||
showError()
|
showError()
|
||||||
|
@ -82,24 +84,25 @@ abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initAdapter() {
|
private fun initAdapter() {
|
||||||
searchRecyclerView.addItemDecoration(DividerItemDecoration(searchRecyclerView.context, DividerItemDecoration.VERTICAL))
|
binding.searchRecyclerView.addItemDecoration(DividerItemDecoration(binding.searchRecyclerView.context, DividerItemDecoration.VERTICAL))
|
||||||
searchRecyclerView.layoutManager = LinearLayoutManager(searchRecyclerView.context)
|
binding.searchRecyclerView.layoutManager = LinearLayoutManager(binding.searchRecyclerView.context)
|
||||||
adapter = createAdapter()
|
adapter = createAdapter()
|
||||||
searchRecyclerView.adapter = adapter
|
binding.searchRecyclerView.adapter = adapter
|
||||||
searchRecyclerView.setHasFixedSize(true)
|
binding.searchRecyclerView.setHasFixedSize(true)
|
||||||
(searchRecyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
(binding.searchRecyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showNoData(isEmpty: Boolean) {
|
private fun showNoData(isEmpty: Boolean) {
|
||||||
if (isEmpty && networkStateRefresh.value == NetworkState.LOADED)
|
if (isEmpty && networkStateRefresh.value == NetworkState.LOADED) {
|
||||||
searchNoResultsText.show()
|
binding.searchNoResultsText.show()
|
||||||
else
|
} else {
|
||||||
searchNoResultsText.hide()
|
binding.searchNoResultsText.hide()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showError() {
|
private fun showError() {
|
||||||
if (snackbarErrorRetry?.isShown != true) {
|
if (snackbarErrorRetry?.isShown != true) {
|
||||||
snackbarErrorRetry = Snackbar.make(layoutRoot, R.string.failed_search, Snackbar.LENGTH_INDEFINITE)
|
snackbarErrorRetry = Snackbar.make(binding.root, R.string.failed_search, Snackbar.LENGTH_INDEFINITE)
|
||||||
snackbarErrorRetry?.setAction(R.string.action_retry) {
|
snackbarErrorRetry?.setAction(R.string.action_retry) {
|
||||||
snackbarErrorRetry = null
|
snackbarErrorRetry = null
|
||||||
viewModel.retryAllSearches()
|
viewModel.retryAllSearches()
|
||||||
|
@ -122,8 +125,8 @@ abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
||||||
override fun onRefresh() {
|
override fun onRefresh() {
|
||||||
|
|
||||||
// Dismissed here because the RecyclerView bottomProgressBar is shown as soon as the retry begins.
|
// Dismissed here because the RecyclerView bottomProgressBar is shown as soon as the retry begins.
|
||||||
swipeRefreshLayout.post {
|
binding.swipeRefreshLayout.post {
|
||||||
swipeRefreshLayout.isRefreshing = false
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
}
|
}
|
||||||
viewModel.retryAllSearches()
|
viewModel.retryAllSearches()
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,6 @@ import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||||
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
||||||
import com.uber.autodispose.autoDispose
|
import com.uber.autodispose.autoDispose
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import kotlinx.android.synthetic.main.fragment_search.*
|
|
||||||
|
|
||||||
class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concrete>>(), StatusActionListener {
|
class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concrete>>(), StatusActionListener {
|
||||||
|
|
||||||
|
@ -78,7 +77,7 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
||||||
get() = super.adapter as SearchStatusesAdapter
|
get() = super.adapter as SearchStatusesAdapter
|
||||||
|
|
||||||
override fun createAdapter(): PagedListAdapter<Pair<Status, StatusViewData.Concrete>, *> {
|
override fun createAdapter(): PagedListAdapter<Pair<Status, StatusViewData.Concrete>, *> {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(searchRecyclerView.context)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(binding.searchRecyclerView.context)
|
||||||
val statusDisplayOptions = StatusDisplayOptions(
|
val statusDisplayOptions = StatusDisplayOptions(
|
||||||
animateAvatars = preferences.getBoolean("animateGifAvatars", false),
|
animateAvatars = preferences.getBoolean("animateGifAvatars", false),
|
||||||
mediaPreviewEnabled = viewModel.mediaPreviewEnabled,
|
mediaPreviewEnabled = viewModel.mediaPreviewEnabled,
|
||||||
|
@ -91,12 +90,11 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
||||||
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
searchRecyclerView.addItemDecoration(DividerItemDecoration(searchRecyclerView.context, DividerItemDecoration.VERTICAL))
|
binding.searchRecyclerView.addItemDecoration(DividerItemDecoration(binding.searchRecyclerView.context, DividerItemDecoration.VERTICAL))
|
||||||
searchRecyclerView.layoutManager = LinearLayoutManager(searchRecyclerView.context)
|
binding.searchRecyclerView.layoutManager = LinearLayoutManager(binding.searchRecyclerView.context)
|
||||||
return SearchStatusesAdapter(statusDisplayOptions, this)
|
return SearchStatusesAdapter(statusDisplayOptions, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onContentHiddenChange(isShowing: Boolean, position: Int) {
|
override fun onContentHiddenChange(isShowing: Boolean, position: Int) {
|
||||||
searchAdapter.getItem(position)?.let {
|
searchAdapter.getItem(position)?.let {
|
||||||
viewModel.contentHiddenChange(it, isShowing)
|
viewModel.contentHiddenChange(it, isShowing)
|
||||||
|
@ -486,5 +484,4 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import com.keylesspalace.tusky.AccountListActivity.Type
|
||||||
import com.keylesspalace.tusky.BaseActivity
|
import com.keylesspalace.tusky.BaseActivity
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.adapter.*
|
import com.keylesspalace.tusky.adapter.*
|
||||||
|
import com.keylesspalace.tusky.databinding.FragmentAccountListBinding
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.entity.Account
|
import com.keylesspalace.tusky.entity.Account
|
||||||
import com.keylesspalace.tusky.entity.Relationship
|
import com.keylesspalace.tusky.entity.Relationship
|
||||||
|
@ -40,12 +41,12 @@ import com.keylesspalace.tusky.settings.PrefKeys
|
||||||
import com.keylesspalace.tusky.util.HttpHeaderLink
|
import com.keylesspalace.tusky.util.HttpHeaderLink
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
import com.keylesspalace.tusky.util.show
|
import com.keylesspalace.tusky.util.show
|
||||||
|
import com.keylesspalace.tusky.util.viewBinding
|
||||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener
|
import com.keylesspalace.tusky.view.EndlessOnScrollListener
|
||||||
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
||||||
import com.uber.autodispose.autoDispose
|
import com.uber.autodispose.autoDispose
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import kotlinx.android.synthetic.main.fragment_account_list.*
|
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -56,6 +57,8 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var api: MastodonApi
|
lateinit var api: MastodonApi
|
||||||
|
|
||||||
|
private val binding by viewBinding(FragmentAccountListBinding::bind)
|
||||||
|
|
||||||
private lateinit var type: Type
|
private lateinit var type: Type
|
||||||
private var id: String? = null
|
private var id: String? = null
|
||||||
|
|
||||||
|
@ -73,12 +76,12 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
recyclerView.setHasFixedSize(true)
|
binding.recyclerView.setHasFixedSize(true)
|
||||||
val layoutManager = LinearLayoutManager(view.context)
|
val layoutManager = LinearLayoutManager(view.context)
|
||||||
recyclerView.layoutManager = layoutManager
|
binding.recyclerView.layoutManager = layoutManager
|
||||||
(recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
(binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||||
|
|
||||||
recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
binding.recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
||||||
|
|
||||||
val pm = PreferenceManager.getDefaultSharedPreferences(view.context)
|
val pm = PreferenceManager.getDefaultSharedPreferences(view.context)
|
||||||
val animateAvatar = pm.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
|
val animateAvatar = pm.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
|
||||||
|
@ -90,7 +93,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
Type.FOLLOW_REQUESTS -> FollowRequestsAdapter(this, animateAvatar, animateEmojis)
|
Type.FOLLOW_REQUESTS -> FollowRequestsAdapter(this, animateAvatar, animateEmojis)
|
||||||
else -> FollowAdapter(this, animateAvatar, animateEmojis)
|
else -> FollowAdapter(this, animateAvatar, animateEmojis)
|
||||||
}
|
}
|
||||||
recyclerView.adapter = adapter
|
binding.recyclerView.adapter = adapter
|
||||||
|
|
||||||
scrollListener = object : EndlessOnScrollListener(layoutManager) {
|
scrollListener = object : EndlessOnScrollListener(layoutManager) {
|
||||||
override fun onLoadMore(totalItemsCount: Int, view: RecyclerView) {
|
override fun onLoadMore(totalItemsCount: Int, view: RecyclerView) {
|
||||||
|
@ -101,7 +104,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recyclerView.addOnScrollListener(scrollListener)
|
binding.recyclerView.addOnScrollListener(scrollListener)
|
||||||
|
|
||||||
fetchAccounts()
|
fetchAccounts()
|
||||||
}
|
}
|
||||||
|
@ -136,7 +139,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
val unmutedUser = mutesAdapter.removeItem(position)
|
val unmutedUser = mutesAdapter.removeItem(position)
|
||||||
|
|
||||||
if (unmutedUser != null) {
|
if (unmutedUser != null) {
|
||||||
Snackbar.make(recyclerView, R.string.confirmation_unmuted, Snackbar.LENGTH_LONG)
|
Snackbar.make(binding.recyclerView, R.string.confirmation_unmuted, Snackbar.LENGTH_LONG)
|
||||||
.setAction(R.string.action_undo) {
|
.setAction(R.string.action_undo) {
|
||||||
mutesAdapter.addItem(unmutedUser, position)
|
mutesAdapter.addItem(unmutedUser, position)
|
||||||
onMute(true, id, position, notifications)
|
onMute(true, id, position, notifications)
|
||||||
|
@ -180,7 +183,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
val unblockedUser = blocksAdapter.removeItem(position)
|
val unblockedUser = blocksAdapter.removeItem(position)
|
||||||
|
|
||||||
if (unblockedUser != null) {
|
if (unblockedUser != null) {
|
||||||
Snackbar.make(recyclerView, R.string.confirmation_unblocked, Snackbar.LENGTH_LONG)
|
Snackbar.make(binding.recyclerView, R.string.confirmation_unblocked, Snackbar.LENGTH_LONG)
|
||||||
.setAction(R.string.action_undo) {
|
.setAction(R.string.action_undo) {
|
||||||
blocksAdapter.addItem(unblockedUser, position)
|
blocksAdapter.addItem(unblockedUser, position)
|
||||||
onBlock(true, id, position)
|
onBlock(true, id, position)
|
||||||
|
@ -260,7 +263,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
fetching = true
|
fetching = true
|
||||||
|
|
||||||
if (fromId != null) {
|
if (fromId != null) {
|
||||||
recyclerView.post { adapter.setBottomLoading(true) }
|
binding.recyclerView.post { adapter.setBottomLoading(true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
getFetchCallByListType(fromId)
|
getFetchCallByListType(fromId)
|
||||||
|
@ -303,14 +306,14 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
fetching = false
|
fetching = false
|
||||||
|
|
||||||
if (adapter.itemCount == 0) {
|
if (adapter.itemCount == 0) {
|
||||||
messageView.show()
|
binding.messageView.show()
|
||||||
messageView.setup(
|
binding.messageView.setup(
|
||||||
R.drawable.elephant_friend_empty,
|
R.drawable.elephant_friend_empty,
|
||||||
R.string.message_empty,
|
R.string.message_empty,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
messageView.hide()
|
binding.messageView.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,15 +342,15 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
Log.e(TAG, "Fetch failure", throwable)
|
Log.e(TAG, "Fetch failure", throwable)
|
||||||
|
|
||||||
if (adapter.itemCount == 0) {
|
if (adapter.itemCount == 0) {
|
||||||
messageView.show()
|
binding.messageView.show()
|
||||||
if (throwable is IOException) {
|
if (throwable is IOException) {
|
||||||
messageView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
binding.messageView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||||
messageView.hide()
|
binding.messageView.hide()
|
||||||
this.fetchAccounts(null)
|
this.fetchAccounts(null)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
binding.messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||||
messageView.hide()
|
binding.messageView.hide()
|
||||||
this.fetchAccounts(null)
|
this.fetchAccounts(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -368,5 +371,4 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.ViewMediaActivity
|
import com.keylesspalace.tusky.ViewMediaActivity
|
||||||
|
import com.keylesspalace.tusky.databinding.FragmentTimelineBinding
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.entity.Attachment
|
import com.keylesspalace.tusky.entity.Attachment
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
|
@ -39,13 +40,13 @@ import com.keylesspalace.tusky.util.LinkHelper
|
||||||
import com.keylesspalace.tusky.util.ThemeUtils
|
import com.keylesspalace.tusky.util.ThemeUtils
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
import com.keylesspalace.tusky.util.show
|
import com.keylesspalace.tusky.util.show
|
||||||
|
import com.keylesspalace.tusky.util.viewBinding
|
||||||
import com.keylesspalace.tusky.view.SquareImageView
|
import com.keylesspalace.tusky.view.SquareImageView
|
||||||
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
||||||
import com.uber.autodispose.android.lifecycle.autoDispose
|
import com.uber.autodispose.android.lifecycle.autoDispose
|
||||||
import io.reactivex.SingleObserver
|
import io.reactivex.SingleObserver
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import kotlinx.android.synthetic.main.fragment_timeline.*
|
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -58,49 +59,36 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFragment, Injectable {
|
class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFragment, Injectable {
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun newInstance(accountId: String, enableSwipeToRefresh:Boolean=true): AccountMediaFragment {
|
|
||||||
val fragment = AccountMediaFragment()
|
|
||||||
val args = Bundle()
|
|
||||||
args.putString(ACCOUNT_ID_ARG, accountId)
|
|
||||||
args.putBoolean(ARG_ENABLE_SWIPE_TO_REFRESH,enableSwipeToRefresh)
|
|
||||||
fragment.arguments = args
|
|
||||||
return fragment
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val ACCOUNT_ID_ARG = "account_id"
|
|
||||||
private const val TAG = "AccountMediaFragment"
|
|
||||||
private const val ARG_ENABLE_SWIPE_TO_REFRESH = "arg.enable.swipe.to.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
private var isSwipeToRefreshEnabled: Boolean = true
|
|
||||||
private var needToRefresh = false
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var api: MastodonApi
|
lateinit var api: MastodonApi
|
||||||
|
|
||||||
|
private val binding by viewBinding(FragmentTimelineBinding::bind)
|
||||||
|
|
||||||
|
private lateinit var accountId: String
|
||||||
|
|
||||||
private val adapter = MediaGridAdapter()
|
private val adapter = MediaGridAdapter()
|
||||||
private val statuses = mutableListOf<Status>()
|
private val statuses = mutableListOf<Status>()
|
||||||
private var fetchingStatus = FetchingStatus.NOT_FETCHING
|
private var fetchingStatus = FetchingStatus.NOT_FETCHING
|
||||||
|
|
||||||
private lateinit var accountId: String
|
private var isSwipeToRefreshEnabled: Boolean = true
|
||||||
|
private var needToRefresh = false
|
||||||
|
|
||||||
private val callback = object : SingleObserver<Response<List<Status>>> {
|
private val callback = object : SingleObserver<Response<List<Status>>> {
|
||||||
override fun onError(t: Throwable) {
|
override fun onError(t: Throwable) {
|
||||||
fetchingStatus = FetchingStatus.NOT_FETCHING
|
fetchingStatus = FetchingStatus.NOT_FETCHING
|
||||||
|
|
||||||
if (isAdded) {
|
if (isAdded) {
|
||||||
swipeRefreshLayout.isRefreshing = false
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
progressBar.visibility = View.GONE
|
binding.progressBar.visibility = View.GONE
|
||||||
topProgressBar?.hide()
|
binding.topProgressBar.hide()
|
||||||
statusView.show()
|
binding.statusView.show()
|
||||||
if (t is IOException) {
|
if (t is IOException) {
|
||||||
statusView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
binding.statusView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||||
doInitialLoadingIfNeeded()
|
doInitialLoadingIfNeeded()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
statusView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
binding.statusView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||||
doInitialLoadingIfNeeded()
|
doInitialLoadingIfNeeded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,9 +100,9 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr
|
||||||
override fun onSuccess(response: Response<List<Status>>) {
|
override fun onSuccess(response: Response<List<Status>>) {
|
||||||
fetchingStatus = FetchingStatus.NOT_FETCHING
|
fetchingStatus = FetchingStatus.NOT_FETCHING
|
||||||
if (isAdded) {
|
if (isAdded) {
|
||||||
swipeRefreshLayout.isRefreshing = false
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
progressBar.visibility = View.GONE
|
binding.progressBar.visibility = View.GONE
|
||||||
topProgressBar?.hide()
|
binding.topProgressBar.hide()
|
||||||
|
|
||||||
val body = response.body()
|
val body = response.body()
|
||||||
body?.let { fetched ->
|
body?.let { fetched ->
|
||||||
|
@ -126,11 +114,11 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr
|
||||||
}
|
}
|
||||||
adapter.addTop(result)
|
adapter.addTop(result)
|
||||||
if (result.isNotEmpty())
|
if (result.isNotEmpty())
|
||||||
recyclerView.scrollToPosition(0)
|
binding.recyclerView.scrollToPosition(0)
|
||||||
|
|
||||||
if (statuses.isEmpty()) {
|
if (statuses.isEmpty()) {
|
||||||
statusView.show()
|
binding.statusView.show()
|
||||||
statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty)
|
binding.statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,18 +169,18 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr
|
||||||
|
|
||||||
adapter.baseItemColor = ThemeUtils.getColor(view.context, android.R.attr.windowBackground)
|
adapter.baseItemColor = ThemeUtils.getColor(view.context, android.R.attr.windowBackground)
|
||||||
|
|
||||||
recyclerView.layoutManager = layoutManager
|
binding.recyclerView.layoutManager = layoutManager
|
||||||
recyclerView.adapter = adapter
|
binding.recyclerView.adapter = adapter
|
||||||
|
|
||||||
if (isSwipeToRefreshEnabled) {
|
if (isSwipeToRefreshEnabled) {
|
||||||
swipeRefreshLayout.setOnRefreshListener {
|
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||||
}
|
}
|
||||||
statusView.visibility = View.GONE
|
binding.statusView.visibility = View.GONE
|
||||||
|
|
||||||
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
|
||||||
override fun onScrolled(recycler_view: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recycler_view: RecyclerView, dx: Int, dy: Int) {
|
||||||
if (dy > 0) {
|
if (dy > 0) {
|
||||||
|
@ -216,7 +204,7 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refresh() {
|
private fun refresh() {
|
||||||
statusView.hide()
|
binding.statusView.hide()
|
||||||
if (fetchingStatus != FetchingStatus.NOT_FETCHING) return
|
if (fetchingStatus != FetchingStatus.NOT_FETCHING) return
|
||||||
if (statuses.isEmpty()) {
|
if (statuses.isEmpty()) {
|
||||||
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
||||||
|
@ -229,12 +217,12 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr
|
||||||
.subscribe(callback)
|
.subscribe(callback)
|
||||||
|
|
||||||
if (!isSwipeToRefreshEnabled)
|
if (!isSwipeToRefreshEnabled)
|
||||||
topProgressBar?.show()
|
binding.topProgressBar.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doInitialLoadingIfNeeded() {
|
private fun doInitialLoadingIfNeeded() {
|
||||||
if (isAdded) {
|
if (isAdded) {
|
||||||
statusView.hide()
|
binding.statusView.hide()
|
||||||
}
|
}
|
||||||
if (fetchingStatus == FetchingStatus.NOT_FETCHING && statuses.isEmpty()) {
|
if (fetchingStatus == FetchingStatus.NOT_FETCHING && statuses.isEmpty()) {
|
||||||
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
||||||
|
@ -344,4 +332,19 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr
|
||||||
needToRefresh = true
|
needToRefresh = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun newInstance(accountId: String, enableSwipeToRefresh:Boolean=true): AccountMediaFragment {
|
||||||
|
val fragment = AccountMediaFragment()
|
||||||
|
val args = Bundle()
|
||||||
|
args.putString(ACCOUNT_ID_ARG, accountId)
|
||||||
|
args.putBoolean(ARG_ENABLE_SWIPE_TO_REFRESH,enableSwipeToRefresh)
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val ACCOUNT_ID_ARG = "account_id"
|
||||||
|
private const val TAG = "AccountMediaFragment"
|
||||||
|
private const val ARG_ENABLE_SWIPE_TO_REFRESH = "arg.enable.swipe.to.refresh"
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -32,13 +32,12 @@ import com.bumptech.glide.load.engine.GlideException
|
||||||
import com.bumptech.glide.request.RequestListener
|
import com.bumptech.glide.request.RequestListener
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.github.chrisbanes.photoview.PhotoViewAttacher
|
import com.github.chrisbanes.photoview.PhotoViewAttacher
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.ViewMediaActivity
|
||||||
|
import com.keylesspalace.tusky.databinding.FragmentViewImageBinding
|
||||||
import com.keylesspalace.tusky.entity.Attachment
|
import com.keylesspalace.tusky.entity.Attachment
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
import com.keylesspalace.tusky.util.visible
|
import com.keylesspalace.tusky.util.visible
|
||||||
import io.reactivex.subjects.BehaviorSubject
|
import io.reactivex.subjects.BehaviorSubject
|
||||||
import kotlinx.android.synthetic.main.activity_view_media.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_view_image.*
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
class ViewImageFragment : ViewMediaFragment() {
|
class ViewImageFragment : ViewMediaFragment() {
|
||||||
|
@ -48,6 +47,9 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
fun onPhotoTap()
|
fun onPhotoTap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var _binding: FragmentViewImageBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private lateinit var attacher: PhotoViewAttacher
|
private lateinit var attacher: PhotoViewAttacher
|
||||||
private lateinit var photoActionsListener: PhotoActionsListener
|
private lateinit var photoActionsListener: PhotoActionsListener
|
||||||
private lateinit var toolbar: View
|
private lateinit var toolbar: View
|
||||||
|
@ -71,18 +73,19 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
description: String?,
|
description: String?,
|
||||||
showingDescription: Boolean
|
showingDescription: Boolean
|
||||||
) {
|
) {
|
||||||
photoView.transitionName = url
|
binding.photoView.transitionName = url
|
||||||
mediaDescription.text = description
|
binding.mediaDescription.text = description
|
||||||
captionSheet.visible(showingDescription)
|
binding.captionSheet.visible(showingDescription)
|
||||||
|
|
||||||
startedTransition = false
|
startedTransition = false
|
||||||
loadImageFromNetwork(url, previewUrl, photoView)
|
loadImageFromNetwork(url, previewUrl, binding.photoView)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
toolbar = requireActivity().toolbar
|
toolbar = (requireActivity() as ViewMediaActivity).toolbar
|
||||||
this.transition = BehaviorSubject.create()
|
this.transition = BehaviorSubject.create()
|
||||||
return inflater.inflate(R.layout.fragment_view_image, container, false)
|
_binding = FragmentViewImageBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
@ -105,7 +108,7 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attacher = PhotoViewAttacher(photoView).apply {
|
attacher = PhotoViewAttacher(binding.photoView).apply {
|
||||||
// This prevents conflicts with ViewPager
|
// This prevents conflicts with ViewPager
|
||||||
setAllowParentInterceptOnEdge(true)
|
setAllowParentInterceptOnEdge(true)
|
||||||
|
|
||||||
|
@ -127,7 +130,7 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
|
|
||||||
var lastY = 0f
|
var lastY = 0f
|
||||||
|
|
||||||
photoView.setOnTouchListener { v, event ->
|
binding.photoView.setOnTouchListener { v, event ->
|
||||||
// This part is for scaling/translating on vertical move.
|
// This part is for scaling/translating on vertical move.
|
||||||
// We use raw coordinates to get the correct ones during scaling
|
// We use raw coordinates to get the correct ones during scaling
|
||||||
|
|
||||||
|
@ -140,11 +143,11 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
val diff = event.rawY - lastY
|
val diff = event.rawY - lastY
|
||||||
// This code is to prevent transformations during page scrolling
|
// This code is to prevent transformations during page scrolling
|
||||||
// If we are already translating or we reached the threshold, then transform.
|
// If we are already translating or we reached the threshold, then transform.
|
||||||
if (photoView.translationY != 0f || abs(diff) > 40) {
|
if (binding.photoView.translationY != 0f || abs(diff) > 40) {
|
||||||
photoView.translationY += (diff)
|
binding.photoView.translationY += (diff)
|
||||||
val scale = (-abs(photoView.translationY) / 720 + 1).coerceAtLeast(0.5f)
|
val scale = (-abs(binding.photoView.translationY) / 720 + 1).coerceAtLeast(0.5f)
|
||||||
photoView.scaleY = scale
|
binding.photoView.scaleY = scale
|
||||||
photoView.scaleX = scale
|
binding.photoView.scaleX = scale
|
||||||
lastY = event.rawY
|
lastY = event.rawY
|
||||||
return@setOnTouchListener true
|
return@setOnTouchListener true
|
||||||
}
|
}
|
||||||
|
@ -158,13 +161,13 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onGestureEnd() {
|
private fun onGestureEnd() {
|
||||||
if (photoView == null) {
|
if (_binding == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (abs(photoView.translationY) > 180) {
|
if (abs(binding.photoView.translationY) > 180) {
|
||||||
photoActionsListener.onDismiss()
|
photoActionsListener.onDismiss()
|
||||||
} else {
|
} else {
|
||||||
photoView.animate().translationY(0f).scaleX(1f).scaleY(1f).start()
|
binding.photoView.animate().translationY(0f).scaleX(1f).scaleY(1f).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,15 +176,17 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onToolbarVisibilityChange(visible: Boolean) {
|
override fun onToolbarVisibilityChange(visible: Boolean) {
|
||||||
if (photoView == null || !userVisibleHint || captionSheet == null) {
|
if (_binding == null || !userVisibleHint ) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isDescriptionVisible = showingDescription && visible
|
isDescriptionVisible = showingDescription && visible
|
||||||
val alpha = if (isDescriptionVisible) 1.0f else 0.0f
|
val alpha = if (isDescriptionVisible) 1.0f else 0.0f
|
||||||
captionSheet.animate().alpha(alpha)
|
binding.captionSheet.animate().alpha(alpha)
|
||||||
.setListener(object : AnimatorListenerAdapter() {
|
.setListener(object : AnimatorListenerAdapter() {
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
captionSheet?.visible(isDescriptionVisible)
|
if (_binding != null) {
|
||||||
|
binding.captionSheet.visible(isDescriptionVisible)
|
||||||
|
}
|
||||||
animation.removeListener(this)
|
animation.removeListener(this)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -189,8 +194,9 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
Glide.with(this).clear(photoView)
|
Glide.with(this).clear(binding.photoView)
|
||||||
transition.onComplete()
|
transition.onComplete()
|
||||||
|
_binding = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +259,7 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
photoActionsListener.onBringUp()
|
photoActionsListener.onBringUp()
|
||||||
}
|
}
|
||||||
// Hide progress bar only on fail request from internet
|
// Hide progress bar only on fail request from internet
|
||||||
if (!isCacheRequest) progressBar?.hide()
|
if (!isCacheRequest && _binding != null) binding.progressBar.hide()
|
||||||
// We don't want to overwrite preview with null when main image fails to load
|
// We don't want to overwrite preview with null when main image fails to load
|
||||||
return !isCacheRequest
|
return !isCacheRequest
|
||||||
}
|
}
|
||||||
|
@ -261,14 +267,16 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
override fun onResourceReady(resource: Drawable, model: Any, target: Target<Drawable>,
|
override fun onResourceReady(resource: Drawable, model: Any, target: Target<Drawable>,
|
||||||
dataSource: DataSource, isFirstResource: Boolean): Boolean {
|
dataSource: DataSource, isFirstResource: Boolean): Boolean {
|
||||||
progressBar?.hide() // Always hide the progress bar on success
|
if (_binding != null) {
|
||||||
|
binding.progressBar.hide() // Always hide the progress bar on success
|
||||||
|
}
|
||||||
|
|
||||||
if (!startedTransition || !shouldStartTransition) {
|
if (!startedTransition || !shouldStartTransition) {
|
||||||
// Set this right away so that we don't have to concurrent post() requests
|
// Set this right away so that we don't have to concurrent post() requests
|
||||||
startedTransition = true
|
startedTransition = true
|
||||||
// post() because load() replaces image with null. Sometimes after we set
|
// post() because load() replaces image with null. Sometimes after we set
|
||||||
// the thumbnail.
|
// the thumbnail.
|
||||||
photoView.post {
|
binding.photoView.post {
|
||||||
target.onResourceReady(resource, null)
|
target.onResourceReady(resource, null)
|
||||||
if (shouldStartTransition) photoActionsListener.onBringUp()
|
if (shouldStartTransition) photoActionsListener.onBringUp()
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,16 +26,18 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.MediaController
|
import android.widget.MediaController
|
||||||
import com.keylesspalace.tusky.R
|
|
||||||
import com.keylesspalace.tusky.ViewMediaActivity
|
import com.keylesspalace.tusky.ViewMediaActivity
|
||||||
|
import com.keylesspalace.tusky.databinding.FragmentViewVideoBinding
|
||||||
import com.keylesspalace.tusky.entity.Attachment
|
import com.keylesspalace.tusky.entity.Attachment
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
import com.keylesspalace.tusky.util.visible
|
import com.keylesspalace.tusky.util.visible
|
||||||
import com.keylesspalace.tusky.view.ExposedPlayPauseVideoView
|
import com.keylesspalace.tusky.view.ExposedPlayPauseVideoView
|
||||||
import kotlinx.android.synthetic.main.activity_view_media.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_view_video.*
|
|
||||||
|
|
||||||
class ViewVideoFragment : ViewMediaFragment() {
|
class ViewVideoFragment : ViewMediaFragment() {
|
||||||
|
|
||||||
|
private var _binding: FragmentViewVideoBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private lateinit var toolbar: View
|
private lateinit var toolbar: View
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
private val hideToolbar = Runnable {
|
private val hideToolbar = Runnable {
|
||||||
|
@ -52,7 +54,7 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
|
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
|
||||||
// Start/pause/resume video playback as fragment is shown/hidden
|
// Start/pause/resume video playback as fragment is shown/hidden
|
||||||
super.setUserVisibleHint(isVisibleToUser)
|
super.setUserVisibleHint(isVisibleToUser)
|
||||||
if (videoView == null) {
|
if (_binding == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,10 +62,10 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
if (mediaActivity.isToolbarVisible) {
|
if (mediaActivity.isToolbarVisible) {
|
||||||
handler.postDelayed(hideToolbar, TOOLBAR_HIDE_DELAY_MS)
|
handler.postDelayed(hideToolbar, TOOLBAR_HIDE_DELAY_MS)
|
||||||
}
|
}
|
||||||
videoView.start()
|
binding.videoView.start()
|
||||||
} else {
|
} else {
|
||||||
handler.removeCallbacks(hideToolbar)
|
handler.removeCallbacks(hideToolbar)
|
||||||
videoView.pause()
|
binding.videoView.pause()
|
||||||
mediaController.hide()
|
mediaController.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,11 +77,11 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
description: String?,
|
description: String?,
|
||||||
showingDescription: Boolean
|
showingDescription: Boolean
|
||||||
) {
|
) {
|
||||||
mediaDescription.text = description
|
binding.mediaDescription.text = description
|
||||||
mediaDescription.visible(showingDescription)
|
binding.mediaDescription.visible(showingDescription)
|
||||||
|
|
||||||
videoView.transitionName = url
|
binding.videoView.transitionName = url
|
||||||
videoView.setVideoPath(url)
|
binding.videoView.setVideoPath(url)
|
||||||
mediaController = object : MediaController(mediaActivity) {
|
mediaController = object : MediaController(mediaActivity) {
|
||||||
override fun show(timeout: Int) {
|
override fun show(timeout: Int) {
|
||||||
// We're doing manual auto-close management.
|
// We're doing manual auto-close management.
|
||||||
|
@ -100,10 +102,10 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaController.setMediaPlayer(videoView)
|
mediaController.setMediaPlayer(binding.videoView)
|
||||||
videoView.setMediaController(mediaController)
|
binding.videoView.setMediaController(mediaController)
|
||||||
videoView.requestFocus()
|
binding.videoView.requestFocus()
|
||||||
videoView.setPlayPauseListener(object: ExposedPlayPauseVideoView.PlayPauseListener {
|
binding.videoView.setPlayPauseListener(object: ExposedPlayPauseVideoView.PlayPauseListener {
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
handler.removeCallbacks(hideToolbar)
|
handler.removeCallbacks(hideToolbar)
|
||||||
}
|
}
|
||||||
|
@ -117,31 +119,31 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
videoView.setOnPreparedListener { mp ->
|
binding.videoView.setOnPreparedListener { mp ->
|
||||||
val containerWidth = videoContainer.measuredWidth.toFloat()
|
val containerWidth = binding.videoContainer.measuredWidth.toFloat()
|
||||||
val containerHeight = videoContainer.measuredHeight.toFloat()
|
val containerHeight = binding.videoContainer.measuredHeight.toFloat()
|
||||||
val videoWidth = mp.videoWidth.toFloat()
|
val videoWidth = mp.videoWidth.toFloat()
|
||||||
val videoHeight = mp.videoHeight.toFloat()
|
val videoHeight = mp.videoHeight.toFloat()
|
||||||
|
|
||||||
if(containerWidth/containerHeight > videoWidth/videoHeight) {
|
if(containerWidth/containerHeight > videoWidth/videoHeight) {
|
||||||
videoView.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
binding.videoView.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
videoView.layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT
|
binding.videoView.layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
} else {
|
} else {
|
||||||
videoView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
binding.videoView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
videoView.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
|
binding.videoView.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until the media is loaded before accepting taps as we don't want toolbar to
|
// Wait until the media is loaded before accepting taps as we don't want toolbar to
|
||||||
// be hidden until then.
|
// be hidden until then.
|
||||||
videoView.setOnTouchListener { _, _ ->
|
binding.videoView.setOnTouchListener { _, _ ->
|
||||||
mediaActivity.onPhotoTap()
|
mediaActivity.onPhotoTap()
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
progressBar.hide()
|
binding.progressBar.hide()
|
||||||
mp.isLooping = true
|
mp.isLooping = true
|
||||||
if (requireArguments().getBoolean(ARG_START_POSTPONED_TRANSITION)) {
|
if (requireArguments().getBoolean(ARG_START_POSTPONED_TRANSITION)) {
|
||||||
videoView.start()
|
binding.videoView.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,9 +157,10 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
toolbar = requireActivity().toolbar
|
|
||||||
mediaActivity = activity as ViewMediaActivity
|
mediaActivity = activity as ViewMediaActivity
|
||||||
return inflater.inflate(R.layout.fragment_view_video, container, false)
|
toolbar = mediaActivity.toolbar
|
||||||
|
_binding = FragmentViewVideoBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
@ -174,7 +177,7 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onToolbarVisibilityChange(visible: Boolean) {
|
override fun onToolbarVisibilityChange(visible: Boolean) {
|
||||||
if (videoView == null || mediaDescription == null || !userVisibleHint) {
|
if (_binding == null || !userVisibleHint) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,20 +185,22 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
val alpha = if (isDescriptionVisible) 1.0f else 0.0f
|
val alpha = if (isDescriptionVisible) 1.0f else 0.0f
|
||||||
if (isDescriptionVisible) {
|
if (isDescriptionVisible) {
|
||||||
// If to be visible, need to make visible immediately and animate alpha
|
// If to be visible, need to make visible immediately and animate alpha
|
||||||
mediaDescription.alpha = 0.0f
|
binding.mediaDescription.alpha = 0.0f
|
||||||
mediaDescription.visible(isDescriptionVisible)
|
binding.mediaDescription.visible(isDescriptionVisible)
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaDescription.animate().alpha(alpha)
|
binding.mediaDescription.animate().alpha(alpha)
|
||||||
.setListener(object : AnimatorListenerAdapter() {
|
.setListener(object : AnimatorListenerAdapter() {
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
mediaDescription?.visible(isDescriptionVisible)
|
if (_binding != null) {
|
||||||
|
binding.mediaDescription.visible(isDescriptionVisible)
|
||||||
|
}
|
||||||
animation.removeListener(this)
|
animation.removeListener(this)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.start()
|
.start()
|
||||||
|
|
||||||
if (visible && videoView.isPlaying && !isAudio) {
|
if (visible && binding.videoView.isPlaying && !isAudio) {
|
||||||
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
|
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
|
||||||
} else {
|
} else {
|
||||||
handler.removeCallbacks(hideToolbar)
|
handler.removeCallbacks(hideToolbar)
|
||||||
|
@ -204,4 +209,9 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
|
|
||||||
override fun onTransitionEnd() {
|
override fun onTransitionEnd() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,67 @@
|
||||||
package com.keylesspalace.tusky.util
|
package com.keylesspalace.tusky.util
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
|
import kotlin.properties.ReadOnlyProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c
|
* https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c
|
||||||
*/
|
*/
|
||||||
|
|
||||||
inline fun <T : ViewBinding> AppCompatActivity.viewBinding(
|
inline fun <T : ViewBinding> AppCompatActivity.viewBinding(
|
||||||
crossinline bindingInflater: (LayoutInflater) -> T
|
crossinline bindingInflater: (LayoutInflater) -> T
|
||||||
) = lazy(LazyThreadSafetyMode.NONE) {
|
) = lazy(LazyThreadSafetyMode.NONE) {
|
||||||
bindingInflater(layoutInflater)
|
bindingInflater(layoutInflater)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FragmentViewBindingDelegate<T : ViewBinding>(
|
||||||
|
val fragment: Fragment,
|
||||||
|
val viewBindingFactory: (View) -> T
|
||||||
|
) : ReadOnlyProperty<Fragment, T> {
|
||||||
|
private var binding: T? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
fragment.lifecycle.addObserver(
|
||||||
|
object : DefaultLifecycleObserver {
|
||||||
|
override fun onCreate(owner: LifecycleOwner) {
|
||||||
|
fragment.viewLifecycleOwnerLiveData.observe(
|
||||||
|
fragment,
|
||||||
|
{ t ->
|
||||||
|
t?.lifecycle?.addObserver(
|
||||||
|
object : DefaultLifecycleObserver {
|
||||||
|
override fun onDestroy(owner: LifecycleOwner) {
|
||||||
|
binding = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
|
||||||
|
val binding = binding
|
||||||
|
if (binding != null) {
|
||||||
|
return binding
|
||||||
|
}
|
||||||
|
|
||||||
|
val lifecycle = fragment.viewLifecycleOwner.lifecycle
|
||||||
|
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
|
||||||
|
throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewBindingFactory(thisRef.requireView()).also { this@FragmentViewBindingDelegate.binding = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) =
|
||||||
|
FragmentViewBindingDelegate(this, viewBindingFactory)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/layoutRoot"
|
|
||||||
android:layout_width="@dimen/timeline_width"
|
android:layout_width="@dimen/timeline_width"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
|
|
@ -32,12 +32,12 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
app:layout_constrainedHeight="true"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:visibility="visible"
|
tools:visibility="visible" />
|
||||||
app:layout_constrainedHeight="true" />
|
|
||||||
|
|
||||||
<androidx.core.widget.ContentLoadingProgressBar
|
<androidx.core.widget.ContentLoadingProgressBar
|
||||||
android:id="@+id/topProgressBar"
|
android:id="@+id/topProgressBar"
|
||||||
|
@ -50,4 +50,5 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in a new issue