add ktlint plugin to project and apply default code style (#2209)
* add ktlint plugin to project and apply default code style * some manual adjustments, fix wildcard imports * update CONTRIBUTING.md * fix formatting
This commit is contained in:
parent
955267199e
commit
16ffcca748
227 changed files with 3933 additions and 3371 deletions
|
@ -2,13 +2,13 @@ package com.keylesspalace.tusky
|
|||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.StringRes
|
||||
import android.text.SpannableString
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.text.style.URLSpan
|
||||
import android.text.util.Linkify
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import com.keylesspalace.tusky.databinding.ActivityAboutBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.util.NoUnderlineURLSpan
|
||||
|
@ -32,7 +32,7 @@ class AboutActivity : BottomSheetActivity(), Injectable {
|
|||
|
||||
binding.versionTextView.text = getString(R.string.about_app_version, getString(R.string.app_name), BuildConfig.VERSION_NAME)
|
||||
|
||||
if(BuildConfig.CUSTOM_INSTANCE.isBlank()) {
|
||||
if (BuildConfig.CUSTOM_INSTANCE.isBlank()) {
|
||||
binding.aboutPoweredByTusky.hide()
|
||||
}
|
||||
|
||||
|
|
|
@ -62,7 +62,16 @@ import com.keylesspalace.tusky.interfaces.LinkListener
|
|||
import com.keylesspalace.tusky.interfaces.ReselectableFragment
|
||||
import com.keylesspalace.tusky.pager.AccountPagerAdapter
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.DefaultTextWatcher
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.util.Success
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.loadAvatar
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
import com.keylesspalace.tusky.view.showMuteAccountDialog
|
||||
import com.keylesspalace.tusky.viewmodel.AccountViewModel
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
|
@ -82,7 +91,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
private val binding: ActivityAccountBinding by viewBinding(ActivityAccountBinding::inflate)
|
||||
|
||||
private lateinit var accountFieldAdapter : AccountFieldAdapter
|
||||
private lateinit var accountFieldAdapter: AccountFieldAdapter
|
||||
|
||||
private var followState: FollowState = FollowState.NOT_FOLLOWING
|
||||
private var blocking: Boolean = false
|
||||
|
@ -233,7 +242,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
override fun onTabUnselected(tab: TabLayout.Tab?) {}
|
||||
|
||||
override fun onTabSelected(tab: TabLayout.Tab?) {}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -266,8 +274,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
fillColor = ColorStateList.valueOf(toolbarColor)
|
||||
elevation = appBarElevation
|
||||
shapeAppearanceModel = ShapeAppearanceModel.builder()
|
||||
.setAllCornerSizes(resources.getDimension(R.dimen.account_avatar_background_radius))
|
||||
.build()
|
||||
.setAllCornerSizes(resources.getDimension(R.dimen.account_avatar_background_radius))
|
||||
.build()
|
||||
}
|
||||
binding.accountAvatarImageView.background = avatarBackground
|
||||
|
||||
|
@ -314,7 +322,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
binding.swipeToRefreshLayout.isEnabled = verticalOffset == 0
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private fun makeNotificationBarTransparent() {
|
||||
|
@ -331,8 +338,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
is Success -> onAccountChanged(it.data)
|
||||
is Error -> {
|
||||
Snackbar.make(binding.accountCoordinatorLayout, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry) { viewModel.refresh() }
|
||||
.show()
|
||||
.setAction(R.string.action_retry) { viewModel.refresh() }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -344,15 +351,17 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
if (it is Error) {
|
||||
Snackbar.make(binding.accountCoordinatorLayout, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry) { viewModel.refresh() }
|
||||
.show()
|
||||
.setAction(R.string.action_retry) { viewModel.refresh() }
|
||||
.show()
|
||||
}
|
||||
|
||||
}
|
||||
viewModel.accountFieldData.observe(this, {
|
||||
accountFieldAdapter.fields = it
|
||||
accountFieldAdapter.notifyDataSetChanged()
|
||||
})
|
||||
viewModel.accountFieldData.observe(
|
||||
this,
|
||||
{
|
||||
accountFieldAdapter.fields = it
|
||||
accountFieldAdapter.notifyDataSetChanged()
|
||||
}
|
||||
)
|
||||
viewModel.noteSaved.observe(this) {
|
||||
binding.saveNoteInfo.visible(it, View.INVISIBLE)
|
||||
}
|
||||
|
@ -366,9 +375,12 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
viewModel.refresh()
|
||||
adapter.refreshContent()
|
||||
}
|
||||
viewModel.isRefreshing.observe(this, { isRefreshing ->
|
||||
binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true
|
||||
})
|
||||
viewModel.isRefreshing.observe(
|
||||
this,
|
||||
{ isRefreshing ->
|
||||
binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true
|
||||
}
|
||||
)
|
||||
binding.swipeToRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
}
|
||||
|
||||
|
@ -382,7 +394,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
val emojifiedNote = account.note.emojify(account.emojis, binding.accountNoteTextView, animateEmojis)
|
||||
LinkHelper.setClickableText(binding.accountNoteTextView, emojifiedNote, null, this)
|
||||
|
||||
// accountFieldAdapter.fields = account.fields ?: emptyList()
|
||||
// accountFieldAdapter.fields = account.fields ?: emptyList()
|
||||
accountFieldAdapter.emojis = account.emojis ?: emptyList()
|
||||
accountFieldAdapter.notifyDataSetChanged()
|
||||
|
||||
|
@ -409,18 +421,17 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
loadedAccount?.let { account ->
|
||||
|
||||
loadAvatar(
|
||||
account.avatar,
|
||||
binding.accountAvatarImageView,
|
||||
resources.getDimensionPixelSize(R.dimen.avatar_radius_94dp),
|
||||
animateAvatar
|
||||
account.avatar,
|
||||
binding.accountAvatarImageView,
|
||||
resources.getDimensionPixelSize(R.dimen.avatar_radius_94dp),
|
||||
animateAvatar
|
||||
)
|
||||
|
||||
Glide.with(this)
|
||||
.asBitmap()
|
||||
.load(account.header)
|
||||
.centerCrop()
|
||||
.into(binding.accountHeaderImageView)
|
||||
|
||||
.asBitmap()
|
||||
.load(account.header)
|
||||
.centerCrop()
|
||||
.into(binding.accountHeaderImageView)
|
||||
|
||||
binding.accountAvatarImageView.setOnClickListener { avatarView ->
|
||||
val intent = ViewMediaActivity.newSingleImageIntent(avatarView.context, account.avatar)
|
||||
|
@ -478,7 +489,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
binding.accountMovedText.setCompoundDrawablesRelativeWithIntrinsicBounds(movedIcon, null, null, null)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -554,15 +564,16 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
// because subscribing is Pleroma extension, enable it __only__ when we have non-null subscribing field
|
||||
// it's also now supported in Mastodon 3.3.0rc but called notifying and use different API call
|
||||
if(!viewModel.isSelf && followState == FollowState.FOLLOWING
|
||||
&& (relation.subscribing != null || relation.notifying != null)) {
|
||||
if (!viewModel.isSelf && followState == FollowState.FOLLOWING &&
|
||||
(relation.subscribing != null || relation.notifying != null)
|
||||
) {
|
||||
binding.accountSubscribeButton.show()
|
||||
binding.accountSubscribeButton.setOnClickListener {
|
||||
viewModel.changeSubscribingState()
|
||||
}
|
||||
if(relation.notifying != null)
|
||||
if (relation.notifying != null)
|
||||
subscribing = relation.notifying
|
||||
else if(relation.subscribing != null)
|
||||
else if (relation.subscribing != null)
|
||||
subscribing = relation.subscribing
|
||||
}
|
||||
|
||||
|
@ -577,7 +588,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
updateButtons()
|
||||
}
|
||||
|
||||
private val noteWatcher = object: DefaultTextWatcher() {
|
||||
private val noteWatcher = object : DefaultTextWatcher() {
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
viewModel.noteChanged(s.toString())
|
||||
}
|
||||
|
@ -615,11 +626,11 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
}
|
||||
|
||||
private fun updateSubscribeButton() {
|
||||
if(followState != FollowState.FOLLOWING) {
|
||||
if (followState != FollowState.FOLLOWING) {
|
||||
binding.accountSubscribeButton.hide()
|
||||
}
|
||||
|
||||
if(subscribing) {
|
||||
if (subscribing) {
|
||||
binding.accountSubscribeButton.setIconResource(R.drawable.ic_notifications_active_24dp)
|
||||
binding.accountSubscribeButton.contentDescription = getString(R.string.action_unsubscribe_account)
|
||||
} else {
|
||||
|
@ -648,7 +659,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
binding.accountMuteButton.hide()
|
||||
updateMuteButton()
|
||||
}
|
||||
|
||||
} else {
|
||||
binding.accountFloatingActionButton.hide()
|
||||
binding.accountFollowButton.hide()
|
||||
|
@ -698,11 +708,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
} else {
|
||||
getString(R.string.action_show_reblogs)
|
||||
}
|
||||
|
||||
} else {
|
||||
menu.removeItem(R.id.action_show_reblogs)
|
||||
}
|
||||
|
||||
} else {
|
||||
// It shouldn't be possible to block, mute or report yourself.
|
||||
menu.removeItem(R.id.action_block)
|
||||
|
@ -717,39 +725,39 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
private fun showFollowRequestPendingDialog() {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.dialog_message_cancel_follow_request)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
.setMessage(R.string.dialog_message_cancel_follow_request)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun showUnfollowWarningDialog() {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.dialog_unfollow_warning)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
.setMessage(R.string.dialog_unfollow_warning)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun toggleBlockDomain(instance: String) {
|
||||
if(blockingDomain) {
|
||||
if (blockingDomain) {
|
||||
viewModel.unblockDomain(instance)
|
||||
} else {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(getString(R.string.mute_domain_warning, instance))
|
||||
.setPositiveButton(getString(R.string.mute_domain_warning_dialog_ok)) { _, _ -> viewModel.blockDomain(instance) }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
.setMessage(getString(R.string.mute_domain_warning, instance))
|
||||
.setPositiveButton(getString(R.string.mute_domain_warning_dialog_ok)) { _, _ -> viewModel.blockDomain(instance) }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleBlock() {
|
||||
if (viewModel.relationshipData.value?.data?.blocking != true) {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(getString(R.string.dialog_block_warning, loadedAccount?.username))
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeBlockState() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
.setMessage(getString(R.string.dialog_block_warning, loadedAccount?.username))
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeBlockState() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
} else {
|
||||
viewModel.changeBlockState()
|
||||
}
|
||||
|
@ -759,8 +767,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
if (viewModel.relationshipData.value?.data?.muting != true) {
|
||||
loadedAccount?.let {
|
||||
showMuteAccountDialog(
|
||||
this,
|
||||
it.username
|
||||
this,
|
||||
it.username
|
||||
) { notifications, duration ->
|
||||
viewModel.muteAccount(notifications, duration)
|
||||
}
|
||||
|
@ -772,8 +780,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
private fun mention() {
|
||||
loadedAccount?.let {
|
||||
val intent = ComposeActivity.startIntent(this,
|
||||
ComposeActivity.ComposeOptions(mentionedUsernames = setOf(it.username)))
|
||||
val intent = ComposeActivity.startIntent(
|
||||
this,
|
||||
ComposeActivity.ComposeOptions(mentionedUsernames = setOf(it.username))
|
||||
)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
@ -849,5 +859,4 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
return intent
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -64,9 +64,9 @@ class AccountListActivity : BaseActivity(), HasAndroidInjector {
|
|||
}
|
||||
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.fragment_container, AccountListFragment.newInstance(type, id, accountLocked))
|
||||
.commit()
|
||||
.beginTransaction()
|
||||
.replace(R.id.fragment_container, AccountListFragment.newInstance(type, id, accountLocked))
|
||||
.commit()
|
||||
}
|
||||
|
||||
override fun androidInjector() = dispatchingAndroidInjector
|
||||
|
|
|
@ -36,7 +36,13 @@ import com.keylesspalace.tusky.di.Injectable
|
|||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
import com.keylesspalace.tusky.util.Either
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.loadAvatar
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel
|
||||
import com.keylesspalace.tusky.viewmodel.State
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
|
@ -93,19 +99,19 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
|||
binding.accountsSearchRecycler.adapter = searchAdapter
|
||||
|
||||
viewModel.state
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this))
|
||||
.subscribe { state ->
|
||||
adapter.submitList(state.accounts.asRightOrNull() ?: listOf())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this))
|
||||
.subscribe { state ->
|
||||
adapter.submitList(state.accounts.asRightOrNull() ?: listOf())
|
||||
|
||||
when (state.accounts) {
|
||||
is Either.Right -> binding.messageView.hide()
|
||||
is Either.Left -> handleError(state.accounts.value)
|
||||
}
|
||||
|
||||
setupSearchView(state)
|
||||
when (state.accounts) {
|
||||
is Either.Right -> binding.messageView.hide()
|
||||
is Either.Left -> handleError(state.accounts.value)
|
||||
}
|
||||
|
||||
setupSearchView(state)
|
||||
}
|
||||
|
||||
binding.searchView.isSubmitButtonEnabled = true
|
||||
binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
|
@ -146,11 +152,15 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
|||
viewModel.load(listId)
|
||||
}
|
||||
if (error is IOException) {
|
||||
binding.messageView.setup(R.drawable.elephant_offline,
|
||||
R.string.error_network, retryAction)
|
||||
binding.messageView.setup(
|
||||
R.drawable.elephant_offline,
|
||||
R.string.error_network, retryAction
|
||||
)
|
||||
} else {
|
||||
binding.messageView.setup(R.drawable.elephant_error,
|
||||
R.string.error_generic, retryAction)
|
||||
binding.messageView.setup(
|
||||
R.drawable.elephant_error,
|
||||
R.string.error_generic, retryAction
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,7 +194,7 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
|||
onRemoveFromList(getItem(holder.bindingAdapterPosition).id)
|
||||
}
|
||||
binding.rejectButton.contentDescription =
|
||||
binding.root.context.getString(R.string.action_remove_from_list)
|
||||
binding.root.context.getString(R.string.action_remove_from_list)
|
||||
|
||||
return holder
|
||||
}
|
||||
|
@ -203,8 +213,8 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
|||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: AccountInfo, newItem: AccountInfo): Boolean {
|
||||
return oldItem.second == newItem.second
|
||||
&& oldItem.first.deepEquals(newItem.first)
|
||||
return oldItem.second == newItem.second &&
|
||||
oldItem.first.deepEquals(newItem.first)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,4 +270,4 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
|||
return AccountsInListFragment().apply { arguments = args }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,6 @@ abstract class BottomSheetActivity : BaseActivity() {
|
|||
|
||||
override fun onSlide(bottomSheet: View, slideOffset: Float) {}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
open fun viewUrl(url: String, lookupFallbackBehavior: PostLookupFallbackBehavior = PostLookupFallbackBehavior.OPEN_IN_BROWSER) {
|
||||
|
@ -70,11 +69,12 @@ abstract class BottomSheetActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
mastodonApi.searchObservable(
|
||||
query = url,
|
||||
resolve = true
|
||||
query = url,
|
||||
resolve = true
|
||||
).observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe({ (accounts, statuses) ->
|
||||
.autoDispose(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe(
|
||||
{ (accounts, statuses) ->
|
||||
if (getCancelSearchRequested(url)) {
|
||||
return@subscribe
|
||||
}
|
||||
|
@ -90,12 +90,14 @@ abstract class BottomSheetActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
performUrlFallbackAction(url, lookupFallbackBehavior)
|
||||
}, {
|
||||
},
|
||||
{
|
||||
if (!getCancelSearchRequested(url)) {
|
||||
onEndSearch(url)
|
||||
performUrlFallbackAction(url, lookupFallbackBehavior)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
onBeginSearch(url)
|
||||
}
|
||||
|
@ -186,20 +188,21 @@ fun looksLikeMastodonUrl(urlString: String): Boolean {
|
|||
}
|
||||
|
||||
if (uri.query != null ||
|
||||
uri.fragment != null ||
|
||||
uri.path == null) {
|
||||
uri.fragment != null ||
|
||||
uri.path == null
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
val path = uri.path
|
||||
return path.matches("^/@[^/]+$".toRegex()) ||
|
||||
path.matches("^/@[^/]+/\\d+$".toRegex()) ||
|
||||
path.matches("^/users/\\w+$".toRegex()) ||
|
||||
path.matches("^/notice/[a-zA-Z0-9]+$".toRegex()) ||
|
||||
path.matches("^/objects/[-a-f0-9]+$".toRegex()) ||
|
||||
path.matches("^/notes/[a-z0-9]+$".toRegex()) ||
|
||||
path.matches("^/display/[-a-f0-9]+$".toRegex()) ||
|
||||
path.matches("^/profile/\\w+$".toRegex())
|
||||
path.matches("^/@[^/]+/\\d+$".toRegex()) ||
|
||||
path.matches("^/users/\\w+$".toRegex()) ||
|
||||
path.matches("^/notice/[a-zA-Z0-9]+$".toRegex()) ||
|
||||
path.matches("^/objects/[-a-f0-9]+$".toRegex()) ||
|
||||
path.matches("^/notes/[a-z0-9]+$".toRegex()) ||
|
||||
path.matches("^/display/[-a-f0-9]+$".toRegex()) ||
|
||||
path.matches("^/profile/\\w+$".toRegex())
|
||||
}
|
||||
|
||||
enum class PostLookupFallbackBehavior {
|
||||
|
|
|
@ -36,18 +36,24 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.resource.bitmap.FitCenter
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
import com.canhub.cropper.CropImage
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.keylesspalace.tusky.adapter.AccountFieldEditAdapter
|
||||
import com.keylesspalace.tusky.databinding.ActivityEditProfileBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.Error
|
||||
import com.keylesspalace.tusky.util.Loading
|
||||
import com.keylesspalace.tusky.util.Resource
|
||||
import com.keylesspalace.tusky.util.Success
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.viewmodel.EditProfileViewModel
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||
import com.mikepenz.iconics.utils.colorInt
|
||||
import com.mikepenz.iconics.utils.sizeDp
|
||||
import com.canhub.cropper.CropImage
|
||||
import javax.inject.Inject
|
||||
|
||||
class EditProfileActivity : BaseActivity(), Injectable {
|
||||
|
@ -110,11 +116,11 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
|
||||
binding.addFieldButton.setOnClickListener {
|
||||
accountFieldEditAdapter.addField()
|
||||
if(accountFieldEditAdapter.itemCount >= MAX_ACCOUNT_FIELDS) {
|
||||
if (accountFieldEditAdapter.itemCount >= MAX_ACCOUNT_FIELDS) {
|
||||
it.isVisible = false
|
||||
}
|
||||
|
||||
binding.scrollView.post{
|
||||
binding.scrollView.post {
|
||||
binding.scrollView.smoothScrollTo(0, it.bottom)
|
||||
}
|
||||
}
|
||||
|
@ -134,23 +140,22 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
accountFieldEditAdapter.setFields(me.source?.fields ?: emptyList())
|
||||
binding.addFieldButton.isEnabled = me.source?.fields?.size ?: 0 < MAX_ACCOUNT_FIELDS
|
||||
|
||||
if(viewModel.avatarData.value == null) {
|
||||
if (viewModel.avatarData.value == null) {
|
||||
Glide.with(this)
|
||||
.load(me.avatar)
|
||||
.placeholder(R.drawable.avatar_default)
|
||||
.transform(
|
||||
FitCenter(),
|
||||
RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp))
|
||||
)
|
||||
.into(binding.avatarPreview)
|
||||
.load(me.avatar)
|
||||
.placeholder(R.drawable.avatar_default)
|
||||
.transform(
|
||||
FitCenter(),
|
||||
RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp))
|
||||
)
|
||||
.into(binding.avatarPreview)
|
||||
}
|
||||
|
||||
if(viewModel.headerData.value == null) {
|
||||
if (viewModel.headerData.value == null) {
|
||||
Glide.with(this)
|
||||
.load(me.header)
|
||||
.into(binding.headerPreview)
|
||||
.load(me.header)
|
||||
.into(binding.headerPreview)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
is Error -> {
|
||||
|
@ -159,7 +164,6 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
viewModel.obtainProfile()
|
||||
}
|
||||
snackbar.show()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -179,20 +183,22 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
observeImage(viewModel.avatarData, binding.avatarPreview, binding.avatarProgressBar, true)
|
||||
observeImage(viewModel.headerData, binding.headerPreview, binding.headerProgressBar, false)
|
||||
|
||||
viewModel.saveData.observe(this, {
|
||||
when(it) {
|
||||
is Success -> {
|
||||
finish()
|
||||
}
|
||||
is Loading -> {
|
||||
binding.saveProgressBar.visibility = View.VISIBLE
|
||||
}
|
||||
is Error -> {
|
||||
onSaveFailure(it.errorMessage)
|
||||
viewModel.saveData.observe(
|
||||
this,
|
||||
{
|
||||
when (it) {
|
||||
is Success -> {
|
||||
finish()
|
||||
}
|
||||
is Loading -> {
|
||||
binding.saveProgressBar.visibility = View.VISIBLE
|
||||
}
|
||||
is Error -> {
|
||||
onSaveFailure(it.errorMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
@ -202,50 +208,56 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
if(!isFinishing) {
|
||||
viewModel.updateProfile(binding.displayNameEditText.text.toString(),
|
||||
binding.noteEditText.text.toString(),
|
||||
binding.lockedCheckBox.isChecked,
|
||||
accountFieldEditAdapter.getFieldData())
|
||||
if (!isFinishing) {
|
||||
viewModel.updateProfile(
|
||||
binding.displayNameEditText.text.toString(),
|
||||
binding.noteEditText.text.toString(),
|
||||
binding.lockedCheckBox.isChecked,
|
||||
accountFieldEditAdapter.getFieldData()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeImage(liveData: LiveData<Resource<Bitmap>>,
|
||||
imageView: ImageView,
|
||||
progressBar: View,
|
||||
roundedCorners: Boolean) {
|
||||
liveData.observe(this, {
|
||||
private fun observeImage(
|
||||
liveData: LiveData<Resource<Bitmap>>,
|
||||
imageView: ImageView,
|
||||
progressBar: View,
|
||||
roundedCorners: Boolean
|
||||
) {
|
||||
liveData.observe(
|
||||
this,
|
||||
{
|
||||
|
||||
when (it) {
|
||||
is Success -> {
|
||||
val glide = Glide.with(imageView)
|
||||
when (it) {
|
||||
is Success -> {
|
||||
val glide = Glide.with(imageView)
|
||||
.load(it.data)
|
||||
|
||||
if (roundedCorners) {
|
||||
glide.transform(
|
||||
FitCenter(),
|
||||
RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp))
|
||||
)
|
||||
}
|
||||
if (roundedCorners) {
|
||||
glide.transform(
|
||||
FitCenter(),
|
||||
RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp))
|
||||
)
|
||||
}
|
||||
|
||||
glide.into(imageView)
|
||||
glide.into(imageView)
|
||||
|
||||
imageView.show()
|
||||
progressBar.hide()
|
||||
}
|
||||
is Loading -> {
|
||||
progressBar.show()
|
||||
}
|
||||
is Error -> {
|
||||
progressBar.hide()
|
||||
if(!it.consumed) {
|
||||
onResizeFailure()
|
||||
it.consumed = true
|
||||
imageView.show()
|
||||
progressBar.hide()
|
||||
}
|
||||
is Loading -> {
|
||||
progressBar.show()
|
||||
}
|
||||
is Error -> {
|
||||
progressBar.hide()
|
||||
if (!it.consumed) {
|
||||
onResizeFailure()
|
||||
it.consumed = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
private fun onMediaPick(pickType: PickType) {
|
||||
|
@ -261,8 +273,11 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
|
||||
grantResults: IntArray) {
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
when (requestCode) {
|
||||
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE -> {
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
|
@ -307,14 +322,16 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
|
||||
private fun save() {
|
||||
if (currentlyPicking != PickType.NOTHING) {
|
||||
return
|
||||
return
|
||||
}
|
||||
|
||||
viewModel.save(binding.displayNameEditText.text.toString(),
|
||||
binding.noteEditText.text.toString(),
|
||||
binding.lockedCheckBox.isChecked,
|
||||
accountFieldEditAdapter.getFieldData(),
|
||||
this)
|
||||
viewModel.save(
|
||||
binding.displayNameEditText.text.toString(),
|
||||
binding.noteEditText.text.toString(),
|
||||
binding.lockedCheckBox.isChecked,
|
||||
accountFieldEditAdapter.getFieldData(),
|
||||
this
|
||||
)
|
||||
}
|
||||
|
||||
private fun onSaveFailure(msg: String?) {
|
||||
|
@ -352,10 +369,10 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
AVATAR_PICK_RESULT -> {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
CropImage.activity(data.data)
|
||||
.setInitialCropWindowPaddingRatio(0f)
|
||||
.setOutputCompressFormat(Bitmap.CompressFormat.PNG)
|
||||
.setAspectRatio(AVATAR_SIZE, AVATAR_SIZE)
|
||||
.start(this)
|
||||
.setInitialCropWindowPaddingRatio(0f)
|
||||
.setOutputCompressFormat(Bitmap.CompressFormat.PNG)
|
||||
.setAspectRatio(AVATAR_SIZE, AVATAR_SIZE)
|
||||
.start(this)
|
||||
} else {
|
||||
endMediaPicking()
|
||||
}
|
||||
|
@ -363,10 +380,10 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
HEADER_PICK_RESULT -> {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
CropImage.activity(data.data)
|
||||
.setInitialCropWindowPaddingRatio(0f)
|
||||
.setOutputCompressFormat(Bitmap.CompressFormat.PNG)
|
||||
.setAspectRatio(HEADER_WIDTH, HEADER_HEIGHT)
|
||||
.start(this)
|
||||
.setInitialCropWindowPaddingRatio(0f)
|
||||
.setOutputCompressFormat(Bitmap.CompressFormat.PNG)
|
||||
.setAspectRatio(HEADER_WIDTH, HEADER_HEIGHT)
|
||||
.start(this)
|
||||
} else {
|
||||
endMediaPicking()
|
||||
}
|
||||
|
@ -383,7 +400,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
}
|
||||
|
||||
private fun beginResize(uri: Uri?) {
|
||||
if(uri == null) {
|
||||
if (uri == null) {
|
||||
currentlyPicking = PickType.NOTHING
|
||||
return
|
||||
}
|
||||
|
@ -409,5 +426,4 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
Snackbar.make(binding.avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG).show()
|
||||
endMediaPicking()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,10 +22,9 @@ import retrofit2.Call
|
|||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
import java.lang.Exception
|
||||
import javax.inject.Inject
|
||||
|
||||
class FiltersActivity: BaseActivity() {
|
||||
class FiltersActivity : BaseActivity() {
|
||||
@Inject
|
||||
lateinit var api: MastodonApi
|
||||
|
||||
|
@ -34,7 +33,7 @@ class FiltersActivity: BaseActivity() {
|
|||
|
||||
private val binding by viewBinding(ActivityFiltersBinding::inflate)
|
||||
|
||||
private lateinit var context : String
|
||||
private lateinit var context: String
|
||||
private lateinit var filters: MutableList<Filter>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -58,7 +57,7 @@ class FiltersActivity: BaseActivity() {
|
|||
|
||||
private fun updateFilter(filter: Filter, itemIndex: Int) {
|
||||
api.updateFilter(filter.id, filter.phrase, filter.context, filter.irreversible, filter.wholeWord, filter.expiresAt)
|
||||
.enqueue(object: Callback<Filter>{
|
||||
.enqueue(object : Callback<Filter> {
|
||||
override fun onFailure(call: Call<Filter>, t: Throwable) {
|
||||
Toast.makeText(this@FiltersActivity, "Error updating filter '${filter.phrase}'", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
@ -80,7 +79,7 @@ class FiltersActivity: BaseActivity() {
|
|||
val filter = filters[itemIndex]
|
||||
if (filter.context.size == 1) {
|
||||
// This is the only context for this filter; delete it
|
||||
api.deleteFilter(filters[itemIndex].id).enqueue(object: Callback<ResponseBody> {
|
||||
api.deleteFilter(filters[itemIndex].id).enqueue(object : Callback<ResponseBody> {
|
||||
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
|
||||
Toast.makeText(this@FiltersActivity, "Error updating filter '${filters[itemIndex].phrase}'", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
@ -94,17 +93,19 @@ class FiltersActivity: BaseActivity() {
|
|||
} else {
|
||||
// Keep the filter, but remove it from this context
|
||||
val oldFilter = filters[itemIndex]
|
||||
val newFilter = Filter(oldFilter.id, oldFilter.phrase, oldFilter.context.filter { c -> c != context },
|
||||
oldFilter.expiresAt, oldFilter.irreversible, oldFilter.wholeWord)
|
||||
val newFilter = Filter(
|
||||
oldFilter.id, oldFilter.phrase, oldFilter.context.filter { c -> c != context },
|
||||
oldFilter.expiresAt, oldFilter.irreversible, oldFilter.wholeWord
|
||||
)
|
||||
updateFilter(newFilter, itemIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFilter(phrase: String, wholeWord: Boolean) {
|
||||
api.createFilter(phrase, listOf(context), false, wholeWord, "").enqueue(object: Callback<Filter> {
|
||||
api.createFilter(phrase, listOf(context), false, wholeWord, "").enqueue(object : Callback<Filter> {
|
||||
override fun onResponse(call: Call<Filter>, response: Response<Filter>) {
|
||||
val filterResponse = response.body()
|
||||
if(response.isSuccessful && filterResponse != null) {
|
||||
if (response.isSuccessful && filterResponse != null) {
|
||||
filters.add(filterResponse)
|
||||
refreshFilterDisplay()
|
||||
eventHub.dispatch(PreferenceChangedEvent(context))
|
||||
|
@ -123,13 +124,13 @@ class FiltersActivity: BaseActivity() {
|
|||
val binding = DialogFilterBinding.inflate(layoutInflater)
|
||||
binding.phraseWholeWord.isChecked = true
|
||||
AlertDialog.Builder(this@FiltersActivity)
|
||||
.setTitle(R.string.filter_addition_dialog_title)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(android.R.string.ok){ _, _ ->
|
||||
createFilter(binding.phraseEditText.text.toString(), binding.phraseWholeWord.isChecked)
|
||||
}
|
||||
.setNeutralButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
.setTitle(R.string.filter_addition_dialog_title)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
createFilter(binding.phraseEditText.text.toString(), binding.phraseWholeWord.isChecked)
|
||||
}
|
||||
.setNeutralButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun setupEditDialogForItem(itemIndex: Int) {
|
||||
|
@ -139,19 +140,21 @@ class FiltersActivity: BaseActivity() {
|
|||
binding.phraseWholeWord.isChecked = filter.wholeWord
|
||||
|
||||
AlertDialog.Builder(this@FiltersActivity)
|
||||
.setTitle(R.string.filter_edit_dialog_title)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(R.string.filter_dialog_update_button) { _, _ ->
|
||||
val oldFilter = filters[itemIndex]
|
||||
val newFilter = Filter(oldFilter.id, binding.phraseEditText.text.toString(), oldFilter.context,
|
||||
oldFilter.expiresAt, oldFilter.irreversible, binding.phraseWholeWord.isChecked)
|
||||
updateFilter(newFilter, itemIndex)
|
||||
}
|
||||
.setNegativeButton(R.string.filter_dialog_remove_button) { _, _ ->
|
||||
deleteFilter(itemIndex)
|
||||
}
|
||||
.setNeutralButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
.setTitle(R.string.filter_edit_dialog_title)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(R.string.filter_dialog_update_button) { _, _ ->
|
||||
val oldFilter = filters[itemIndex]
|
||||
val newFilter = Filter(
|
||||
oldFilter.id, binding.phraseEditText.text.toString(), oldFilter.context,
|
||||
oldFilter.expiresAt, oldFilter.irreversible, binding.phraseWholeWord.isChecked
|
||||
)
|
||||
updateFilter(newFilter, itemIndex)
|
||||
}
|
||||
.setNegativeButton(R.string.filter_dialog_remove_button) { _, _ ->
|
||||
deleteFilter(itemIndex)
|
||||
}
|
||||
.setNeutralButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun refreshFilterDisplay() {
|
||||
|
@ -173,11 +176,15 @@ class FiltersActivity: BaseActivity() {
|
|||
binding.filterProgressBar.hide()
|
||||
binding.filterMessageView.show()
|
||||
if (t is IOException) {
|
||||
binding.filterMessageView.setup(R.drawable.elephant_offline,
|
||||
R.string.error_network) { loadFilters() }
|
||||
binding.filterMessageView.setup(
|
||||
R.drawable.elephant_offline,
|
||||
R.string.error_network
|
||||
) { loadFilters() }
|
||||
} else {
|
||||
binding.filterMessageView.setup(R.drawable.elephant_error,
|
||||
R.string.error_generic) { loadFilters() }
|
||||
binding.filterMessageView.setup(
|
||||
R.drawable.elephant_error,
|
||||
R.string.error_generic
|
||||
) { loadFilters() }
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
@ -195,4 +202,4 @@ class FiltersActivity: BaseActivity() {
|
|||
const val FILTERS_CONTEXT = "filters_context"
|
||||
const val FILTERS_TITLE = "filters_title"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
package com.keylesspalace.tusky
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.RawRes
|
||||
import android.util.Log
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.RawRes
|
||||
import com.keylesspalace.tusky.databinding.ActivityLicenseBinding
|
||||
import com.keylesspalace.tusky.util.IOUtils
|
||||
import java.io.BufferedReader
|
||||
|
@ -41,7 +41,6 @@ class LicenseActivity : BaseActivity() {
|
|||
setTitle(R.string.title_licenses)
|
||||
|
||||
loadFileIntoTextView(R.raw.apache, binding.licenseApacheTextView)
|
||||
|
||||
}
|
||||
|
||||
private fun loadFileIntoTextView(@RawRes fileId: Int, textView: TextView) {
|
||||
|
|
|
@ -23,25 +23,41 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import android.widget.EditText
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageButton
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.*
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import at.connyduck.sparkbutton.helpers.Utils
|
||||
import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.from
|
||||
import autodispose2.autoDispose
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.keylesspalace.tusky.components.timeline.TimelineViewModel
|
||||
import com.keylesspalace.tusky.databinding.ActivityListsBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.entity.MastoList
|
||||
import com.keylesspalace.tusky.components.timeline.TimelineViewModel
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.onTextChanged
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel
|
||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel.Event.*
|
||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.*
|
||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel.Event
|
||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.ERROR_NETWORK
|
||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.ERROR_OTHER
|
||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.INITIAL
|
||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.LOADED
|
||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.LOADING
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||
import com.mikepenz.iconics.utils.colorInt
|
||||
|
@ -84,12 +100,13 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
binding.listsRecycler.adapter = adapter
|
||||
binding.listsRecycler.layoutManager = LinearLayoutManager(this)
|
||||
binding.listsRecycler.addItemDecoration(
|
||||
DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
|
||||
DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
|
||||
)
|
||||
|
||||
viewModel.state
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this))
|
||||
.subscribe(this::update)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this))
|
||||
.subscribe(this::update)
|
||||
viewModel.retryLoading()
|
||||
|
||||
binding.addListButton.setOnClickListener {
|
||||
|
@ -97,15 +114,15 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
}
|
||||
|
||||
viewModel.events.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this))
|
||||
.subscribe { event ->
|
||||
@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
|
||||
when (event) {
|
||||
CREATE_ERROR -> showMessage(R.string.error_create_list)
|
||||
RENAME_ERROR -> showMessage(R.string.error_rename_list)
|
||||
DELETE_ERROR -> showMessage(R.string.error_delete_list)
|
||||
}
|
||||
.autoDispose(from(this))
|
||||
.subscribe { event ->
|
||||
@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
|
||||
when (event) {
|
||||
Event.CREATE_ERROR -> showMessage(R.string.error_create_list)
|
||||
Event.RENAME_ERROR -> showMessage(R.string.error_rename_list)
|
||||
Event.DELETE_ERROR -> showMessage(R.string.error_delete_list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showlistNameDialog(list: MastoList?) {
|
||||
|
@ -115,17 +132,18 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
layout.addView(editText)
|
||||
val margin = Utils.dpToPx(this, 8)
|
||||
(editText.layoutParams as ViewGroup.MarginLayoutParams)
|
||||
.setMargins(margin, margin, margin, 0)
|
||||
.setMargins(margin, margin, margin, 0)
|
||||
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setView(layout)
|
||||
.setPositiveButton(
|
||||
if (list == null) R.string.action_create_list
|
||||
else R.string.action_rename_list) { _, _ ->
|
||||
onPickedDialogName(editText.text, list?.id)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
.setView(layout)
|
||||
.setPositiveButton(
|
||||
if (list == null) R.string.action_create_list
|
||||
else R.string.action_rename_list
|
||||
) { _, _ ->
|
||||
onPickedDialogName(editText.text, list?.id)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
|
||||
val positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE)
|
||||
editText.onTextChanged { s, _, _, _ ->
|
||||
|
@ -137,15 +155,14 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
|
||||
private fun showListDeleteDialog(list: MastoList) {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(getString(R.string.dialog_delete_list_warning, list.title))
|
||||
.setPositiveButton(R.string.action_delete){ _, _ ->
|
||||
viewModel.deleteList(list.id)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
.setMessage(getString(R.string.dialog_delete_list_warning, list.title))
|
||||
.setPositiveButton(R.string.action_delete) { _, _ ->
|
||||
viewModel.deleteList(list.id)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
|
||||
private fun update(state: ListsViewModel.State) {
|
||||
adapter.submitList(state.lists)
|
||||
binding.progressBar.visible(state.loadingState == LOADING)
|
||||
|
@ -166,8 +183,10 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
LOADED ->
|
||||
if (state.lists.isEmpty()) {
|
||||
binding.messageView.show()
|
||||
binding.messageView.setup(R.drawable.elephant_friend_empty, R.string.message_empty,
|
||||
null)
|
||||
binding.messageView.setup(
|
||||
R.drawable.elephant_friend_empty, R.string.message_empty,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
binding.messageView.hide()
|
||||
}
|
||||
|
@ -176,13 +195,14 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
|
||||
private fun showMessage(@StringRes messageId: Int) {
|
||||
Snackbar.make(
|
||||
binding.listsRecycler, messageId, Snackbar.LENGTH_SHORT
|
||||
binding.listsRecycler, messageId, Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun onListSelected(listId: String) {
|
||||
startActivityWithSlideInAnimation(
|
||||
ModalTimelineActivity.newIntent(this, TimelineViewModel.Kind.LIST, listId))
|
||||
ModalTimelineActivity.newIntent(this, TimelineViewModel.Kind.LIST, listId)
|
||||
)
|
||||
}
|
||||
|
||||
private fun openListSettings(list: MastoList) {
|
||||
|
@ -219,27 +239,28 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
}
|
||||
}
|
||||
|
||||
private inner class ListsAdapter
|
||||
: ListAdapter<MastoList, ListsAdapter.ListViewHolder>(ListsDiffer) {
|
||||
private inner class ListsAdapter :
|
||||
ListAdapter<MastoList, ListsAdapter.ListViewHolder>(ListsDiffer) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
|
||||
return LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false)
|
||||
.let(this::ListViewHolder)
|
||||
.apply {
|
||||
val context = nameTextView.context
|
||||
val iconColor = ThemeUtils.getColor(context, android.R.attr.textColorTertiary)
|
||||
val icon = IconicsDrawable(context, GoogleMaterial.Icon.gmd_list).apply { sizeDp = 20; colorInt = iconColor }
|
||||
.let(this::ListViewHolder)
|
||||
.apply {
|
||||
val context = nameTextView.context
|
||||
val iconColor = ThemeUtils.getColor(context, android.R.attr.textColorTertiary)
|
||||
val icon = IconicsDrawable(context, GoogleMaterial.Icon.gmd_list).apply { sizeDp = 20; colorInt = iconColor }
|
||||
|
||||
nameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null)
|
||||
}
|
||||
nameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
|
||||
holder.nameTextView.text = getItem(position).title
|
||||
}
|
||||
|
||||
private inner class ListViewHolder(view: View) : RecyclerView.ViewHolder(view),
|
||||
View.OnClickListener {
|
||||
private inner class ListViewHolder(view: View) :
|
||||
RecyclerView.ViewHolder(view),
|
||||
View.OnClickListener {
|
||||
val nameTextView: TextView = view.findViewById(R.id.list_name_textview)
|
||||
val moreButton: ImageButton = view.findViewById(R.id.editListButton)
|
||||
|
||||
|
@ -271,4 +292,4 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
companion object {
|
||||
fun newIntent(context: Context) = Intent(context, ListsActivity::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,11 @@ import com.keylesspalace.tusky.di.Injectable
|
|||
import com.keylesspalace.tusky.entity.AccessToken
|
||||
import com.keylesspalace.tusky.entity.AppCredentials
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.getNonNullString
|
||||
import com.keylesspalace.tusky.util.rickRoll
|
||||
import com.keylesspalace.tusky.util.shouldRickRoll
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import okhttp3.HttpUrl
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
|
@ -62,28 +66,29 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
|
||||
setContentView(binding.root)
|
||||
|
||||
if(savedInstanceState == null && BuildConfig.CUSTOM_INSTANCE.isNotBlank() && !isAdditionalLogin()) {
|
||||
if (savedInstanceState == null && BuildConfig.CUSTOM_INSTANCE.isNotBlank() && !isAdditionalLogin()) {
|
||||
binding.domainEditText.setText(BuildConfig.CUSTOM_INSTANCE)
|
||||
binding.domainEditText.setSelection(BuildConfig.CUSTOM_INSTANCE.length)
|
||||
}
|
||||
|
||||
if(BuildConfig.CUSTOM_LOGO_URL.isNotBlank()) {
|
||||
if (BuildConfig.CUSTOM_LOGO_URL.isNotBlank()) {
|
||||
Glide.with(binding.loginLogo)
|
||||
.load(BuildConfig.CUSTOM_LOGO_URL)
|
||||
.placeholder(null)
|
||||
.into(binding.loginLogo)
|
||||
.load(BuildConfig.CUSTOM_LOGO_URL)
|
||||
.placeholder(null)
|
||||
.into(binding.loginLogo)
|
||||
}
|
||||
|
||||
preferences = getSharedPreferences(
|
||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE)
|
||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE
|
||||
)
|
||||
|
||||
binding.loginButton.setOnClickListener { onButtonClick() }
|
||||
|
||||
binding.whatsAnInstanceTextView.setOnClickListener {
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setMessage(R.string.dialog_whats_an_instance)
|
||||
.setPositiveButton(R.string.action_close, null)
|
||||
.show()
|
||||
.setMessage(R.string.dialog_whats_an_instance)
|
||||
.setPositiveButton(R.string.action_close, null)
|
||||
.show()
|
||||
val textView = dialog.findViewById<TextView>(android.R.id.message)
|
||||
textView?.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
@ -95,7 +100,6 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
} else {
|
||||
binding.toolbar.visibility = View.GONE
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun requiresLogin(): Boolean {
|
||||
|
@ -104,7 +108,7 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
|
||||
override fun finish() {
|
||||
super.finish()
|
||||
if(isAdditionalLogin()) {
|
||||
if (isAdditionalLogin()) {
|
||||
overridePendingTransition(R.anim.slide_from_left, R.anim.slide_to_right)
|
||||
}
|
||||
}
|
||||
|
@ -134,8 +138,10 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
}
|
||||
|
||||
val callback = object : Callback<AppCredentials> {
|
||||
override fun onResponse(call: Call<AppCredentials>,
|
||||
response: Response<AppCredentials>) {
|
||||
override fun onResponse(
|
||||
call: Call<AppCredentials>,
|
||||
response: Response<AppCredentials>
|
||||
) {
|
||||
if (!response.isSuccessful) {
|
||||
binding.loginButton.isEnabled = true
|
||||
binding.domainTextInputLayout.error = getString(R.string.error_failed_app_registration)
|
||||
|
@ -148,10 +154,10 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
val clientSecret = credentials.clientSecret
|
||||
|
||||
preferences.edit()
|
||||
.putString("domain", domain)
|
||||
.putString("clientId", clientId)
|
||||
.putString("clientSecret", clientSecret)
|
||||
.apply()
|
||||
.putString("domain", domain)
|
||||
.putString("clientId", clientId)
|
||||
.putString("clientSecret", clientSecret)
|
||||
.apply()
|
||||
|
||||
redirectUserToAuthorizeAndLogin(domain, clientId)
|
||||
}
|
||||
|
@ -165,11 +171,12 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
}
|
||||
|
||||
mastodonApi
|
||||
.authenticateApp(domain, getString(R.string.app_name), oauthRedirectUri,
|
||||
OAUTH_SCOPES, getString(R.string.tusky_website))
|
||||
.enqueue(callback)
|
||||
.authenticateApp(
|
||||
domain, getString(R.string.app_name), oauthRedirectUri,
|
||||
OAUTH_SCOPES, getString(R.string.tusky_website)
|
||||
)
|
||||
.enqueue(callback)
|
||||
setLoading(true)
|
||||
|
||||
}
|
||||
|
||||
private fun redirectUserToAuthorizeAndLogin(domain: String, clientId: String) {
|
||||
|
@ -177,10 +184,10 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
* login there, and the server will redirect back to the app with its response. */
|
||||
val endpoint = MastodonApi.ENDPOINT_AUTHORIZE
|
||||
val parameters = mapOf(
|
||||
"client_id" to clientId,
|
||||
"redirect_uri" to oauthRedirectUri,
|
||||
"response_type" to "code",
|
||||
"scope" to OAUTH_SCOPES
|
||||
"client_id" to clientId,
|
||||
"redirect_uri" to oauthRedirectUri,
|
||||
"response_type" to "code",
|
||||
"scope" to OAUTH_SCOPES
|
||||
)
|
||||
val url = "https://" + domain + endpoint + "?" + toQueryString(parameters)
|
||||
val uri = Uri.parse(url)
|
||||
|
@ -224,31 +231,27 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
} else {
|
||||
setLoading(false)
|
||||
binding.domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
|
||||
Log.e(TAG, String.format("%s %s",
|
||||
getString(R.string.error_retrieving_oauth_token),
|
||||
response.message()))
|
||||
Log.e(TAG, "%s %s".format(getString(R.string.error_retrieving_oauth_token), response.message()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<AccessToken>, t: Throwable) {
|
||||
setLoading(false)
|
||||
binding.domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
|
||||
Log.e(TAG, String.format("%s %s",
|
||||
getString(R.string.error_retrieving_oauth_token),
|
||||
t.message))
|
||||
Log.e(TAG, "%s %s".format(getString(R.string.error_retrieving_oauth_token), t.message))
|
||||
}
|
||||
}
|
||||
|
||||
mastodonApi.fetchOAuthToken(domain, clientId, clientSecret, redirectUri, code,
|
||||
"authorization_code").enqueue(callback)
|
||||
mastodonApi.fetchOAuthToken(
|
||||
domain, clientId, clientSecret, redirectUri, code,
|
||||
"authorization_code"
|
||||
).enqueue(callback)
|
||||
} else if (error != null) {
|
||||
/* Authorization failed. Put the error response where the user can read it and they
|
||||
* can try again. */
|
||||
setLoading(false)
|
||||
binding.domainTextInputLayout.error = getString(R.string.error_authorization_denied)
|
||||
Log.e(TAG, String.format("%s %s",
|
||||
getString(R.string.error_authorization_denied),
|
||||
error))
|
||||
Log.e(TAG, "%s %s".format(getString(R.string.error_authorization_denied), error))
|
||||
} else {
|
||||
// This case means a junk response was received somehow.
|
||||
setLoading(false)
|
||||
|
@ -340,14 +343,14 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
val navigationbarDividerColor = ThemeUtils.getColor(context, R.attr.dividerColor)
|
||||
|
||||
val colorSchemeParams = CustomTabColorSchemeParams.Builder()
|
||||
.setToolbarColor(toolbarColor)
|
||||
.setNavigationBarColor(navigationbarColor)
|
||||
.setNavigationBarDividerColor(navigationbarDividerColor)
|
||||
.build()
|
||||
.setToolbarColor(toolbarColor)
|
||||
.setNavigationBarColor(navigationbarColor)
|
||||
.setNavigationBarDividerColor(navigationbarDividerColor)
|
||||
.build()
|
||||
|
||||
val customTabsIntent = CustomTabsIntent.Builder()
|
||||
.setDefaultColorSchemeParams(colorSchemeParams)
|
||||
.build()
|
||||
.setDefaultColorSchemeParams(colorSchemeParams)
|
||||
.build()
|
||||
|
||||
try {
|
||||
customTabsIntent.launchUrl(context, uri)
|
||||
|
|
|
@ -4,9 +4,9 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.keylesspalace.tusky.databinding.ActivityModalTimelineBinding
|
||||
import com.keylesspalace.tusky.components.timeline.TimelineFragment
|
||||
import com.keylesspalace.tusky.components.timeline.TimelineViewModel
|
||||
import com.keylesspalace.tusky.databinding.ActivityModalTimelineBinding
|
||||
import com.keylesspalace.tusky.interfaces.ActionButtonActivity
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
|
@ -31,11 +31,11 @@ class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasAn
|
|||
|
||||
if (supportFragmentManager.findFragmentById(R.id.contentFrame) == null) {
|
||||
val kind = intent?.getSerializableExtra(ARG_KIND) as? TimelineViewModel.Kind
|
||||
?: TimelineViewModel.Kind.HOME
|
||||
?: TimelineViewModel.Kind.HOME
|
||||
val argument = intent?.getStringExtra(ARG_ARG)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.contentFrame, TimelineFragment.newInstance(kind, argument))
|
||||
.commit()
|
||||
.replace(R.id.contentFrame, TimelineFragment.newInstance(kind, argument))
|
||||
.commit()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,13 +48,15 @@ class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasAn
|
|||
private const val ARG_ARG = "arg"
|
||||
|
||||
@JvmStatic
|
||||
fun newIntent(context: Context, kind: TimelineViewModel.Kind,
|
||||
argument: String?): Intent {
|
||||
fun newIntent(
|
||||
context: Context,
|
||||
kind: TimelineViewModel.Kind,
|
||||
argument: String?
|
||||
): Intent {
|
||||
val intent = Intent(context, ModalTimelineActivity::class.java)
|
||||
intent.putExtra(ARG_KIND, kind)
|
||||
intent.putExtra(ARG_ARG, argument)
|
||||
return intent
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,9 @@ package com.keylesspalace.tusky
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.keylesspalace.tusky.components.notifications.NotificationHelper
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
|
||||
import com.keylesspalace.tusky.components.notifications.NotificationHelper
|
||||
import javax.inject.Inject
|
||||
|
||||
class SplashActivity : AppCompatActivity(), Injectable {
|
||||
|
@ -46,5 +45,4 @@ class SplashActivity : AppCompatActivity(), Injectable {
|
|||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,15 +19,12 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.commit
|
||||
import com.keylesspalace.tusky.databinding.ActivityStatuslistBinding
|
||||
|
||||
import com.keylesspalace.tusky.components.timeline.TimelineFragment
|
||||
import com.keylesspalace.tusky.components.timeline.TimelineViewModel.Kind
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
import com.keylesspalace.tusky.databinding.ActivityStatuslistBinding
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import javax.inject.Inject
|
||||
|
||||
class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
||||
|
||||
|
@ -44,7 +41,7 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
|
||||
val title = if(kind == Kind.FAVOURITES) {
|
||||
val title = if (kind == Kind.FAVOURITES) {
|
||||
R.string.title_favourites
|
||||
} else {
|
||||
R.string.title_bookmarks
|
||||
|
@ -60,7 +57,6 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
val fragment = TimelineFragment.newInstance(kind)
|
||||
replace(R.id.fragment_container, fragment)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun androidInjector() = dispatchingAndroidInjector
|
||||
|
@ -71,15 +67,14 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
|
||||
@JvmStatic
|
||||
fun newFavouritesIntent(context: Context) =
|
||||
Intent(context, StatusListActivity::class.java).apply {
|
||||
putExtra(EXTRA_KIND, Kind.FAVOURITES.name)
|
||||
}
|
||||
Intent(context, StatusListActivity::class.java).apply {
|
||||
putExtra(EXTRA_KIND, Kind.FAVOURITES.name)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun newBookmarksIntent(context: Context) =
|
||||
Intent(context, StatusListActivity::class.java).apply {
|
||||
putExtra(EXTRA_KIND, Kind.BOOKMARKS.name)
|
||||
}
|
||||
Intent(context, StatusListActivity::class.java).apply {
|
||||
putExtra(EXTRA_KIND, Kind.BOOKMARKS.name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,9 +20,9 @@ import androidx.annotation.DrawableRes
|
|||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.keylesspalace.tusky.components.conversation.ConversationsFragment
|
||||
import com.keylesspalace.tusky.fragment.NotificationsFragment
|
||||
import com.keylesspalace.tusky.components.timeline.TimelineFragment
|
||||
import com.keylesspalace.tusky.components.timeline.TimelineViewModel
|
||||
import com.keylesspalace.tusky.fragment.NotificationsFragment
|
||||
|
||||
/** this would be a good case for a sealed class, but that does not work nice with Room */
|
||||
|
||||
|
@ -34,71 +34,72 @@ const val DIRECT = "Direct"
|
|||
const val HASHTAG = "Hashtag"
|
||||
const val LIST = "List"
|
||||
|
||||
data class TabData(val id: String,
|
||||
@StringRes val text: Int,
|
||||
@DrawableRes val icon: Int,
|
||||
val fragment: (List<String>) -> Fragment,
|
||||
val arguments: List<String> = emptyList(),
|
||||
val title: (Context) -> String = { context -> context.getString(text)}
|
||||
)
|
||||
data class TabData(
|
||||
val id: String,
|
||||
@StringRes val text: Int,
|
||||
@DrawableRes val icon: Int,
|
||||
val fragment: (List<String>) -> Fragment,
|
||||
val arguments: List<String> = emptyList(),
|
||||
val title: (Context) -> String = { context -> context.getString(text) }
|
||||
)
|
||||
|
||||
fun createTabDataFromId(id: String, arguments: List<String> = emptyList()): TabData {
|
||||
return when (id) {
|
||||
HOME -> TabData(
|
||||
HOME,
|
||||
R.string.title_home,
|
||||
R.drawable.ic_home_24dp,
|
||||
{ TimelineFragment.newInstance(TimelineViewModel.Kind.HOME) }
|
||||
HOME,
|
||||
R.string.title_home,
|
||||
R.drawable.ic_home_24dp,
|
||||
{ TimelineFragment.newInstance(TimelineViewModel.Kind.HOME) }
|
||||
)
|
||||
NOTIFICATIONS -> TabData(
|
||||
NOTIFICATIONS,
|
||||
R.string.title_notifications,
|
||||
R.drawable.ic_notifications_24dp,
|
||||
{ NotificationsFragment.newInstance() }
|
||||
NOTIFICATIONS,
|
||||
R.string.title_notifications,
|
||||
R.drawable.ic_notifications_24dp,
|
||||
{ NotificationsFragment.newInstance() }
|
||||
)
|
||||
LOCAL -> TabData(
|
||||
LOCAL,
|
||||
R.string.title_public_local,
|
||||
R.drawable.ic_local_24dp,
|
||||
{ TimelineFragment.newInstance(TimelineViewModel.Kind.PUBLIC_LOCAL) }
|
||||
LOCAL,
|
||||
R.string.title_public_local,
|
||||
R.drawable.ic_local_24dp,
|
||||
{ TimelineFragment.newInstance(TimelineViewModel.Kind.PUBLIC_LOCAL) }
|
||||
)
|
||||
FEDERATED -> TabData(
|
||||
FEDERATED,
|
||||
R.string.title_public_federated,
|
||||
R.drawable.ic_public_24dp,
|
||||
{ TimelineFragment.newInstance(TimelineViewModel.Kind.PUBLIC_FEDERATED) }
|
||||
FEDERATED,
|
||||
R.string.title_public_federated,
|
||||
R.drawable.ic_public_24dp,
|
||||
{ TimelineFragment.newInstance(TimelineViewModel.Kind.PUBLIC_FEDERATED) }
|
||||
)
|
||||
DIRECT -> TabData(
|
||||
DIRECT,
|
||||
R.string.title_direct_messages,
|
||||
R.drawable.ic_reblog_direct_24dp,
|
||||
{ ConversationsFragment.newInstance() }
|
||||
DIRECT,
|
||||
R.string.title_direct_messages,
|
||||
R.drawable.ic_reblog_direct_24dp,
|
||||
{ ConversationsFragment.newInstance() }
|
||||
)
|
||||
HASHTAG -> TabData(
|
||||
HASHTAG,
|
||||
R.string.hashtags,
|
||||
R.drawable.ic_hashtag,
|
||||
{ args -> TimelineFragment.newHashtagInstance(args) },
|
||||
arguments,
|
||||
{ context -> arguments.joinToString(separator = " ") { context.getString(R.string.title_tag, it) }}
|
||||
HASHTAG,
|
||||
R.string.hashtags,
|
||||
R.drawable.ic_hashtag,
|
||||
{ args -> TimelineFragment.newHashtagInstance(args) },
|
||||
arguments,
|
||||
{ context -> arguments.joinToString(separator = " ") { context.getString(R.string.title_tag, it) } }
|
||||
)
|
||||
LIST -> TabData(
|
||||
LIST,
|
||||
R.string.list,
|
||||
R.drawable.ic_list,
|
||||
{ args -> TimelineFragment.newInstance(TimelineViewModel.Kind.LIST, args.getOrNull(0).orEmpty()) },
|
||||
arguments,
|
||||
{ arguments.getOrNull(1).orEmpty() }
|
||||
)
|
||||
LIST,
|
||||
R.string.list,
|
||||
R.drawable.ic_list,
|
||||
{ args -> TimelineFragment.newInstance(TimelineViewModel.Kind.LIST, args.getOrNull(0).orEmpty()) },
|
||||
arguments,
|
||||
{ arguments.getOrNull(1).orEmpty() }
|
||||
)
|
||||
else -> throw IllegalArgumentException("unknown tab type")
|
||||
}
|
||||
}
|
||||
|
||||
fun defaultTabs(): List<TabData> {
|
||||
return listOf(
|
||||
createTabDataFromId(HOME),
|
||||
createTabDataFromId(NOTIFICATIONS),
|
||||
createTabDataFromId(LOCAL),
|
||||
createTabDataFromId(FEDERATED)
|
||||
createTabDataFromId(HOME),
|
||||
createTabDataFromId(NOTIFICATIONS),
|
||||
createTabDataFromId(LOCAL),
|
||||
createTabDataFromId(FEDERATED)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -221,26 +221,26 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
frameLayout.addView(editText)
|
||||
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setTitle(R.string.add_hashtag_title)
|
||||
.setView(frameLayout)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.action_save) { _, _ ->
|
||||
val input = editText.text.toString().trim()
|
||||
if (tab == null) {
|
||||
val newTab = createTabDataFromId(HASHTAG, listOf(input))
|
||||
currentTabs.add(newTab)
|
||||
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
||||
} else {
|
||||
val newTab = tab.copy(arguments = tab.arguments + input)
|
||||
currentTabs[tabPosition] = newTab
|
||||
.setTitle(R.string.add_hashtag_title)
|
||||
.setView(frameLayout)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.action_save) { _, _ ->
|
||||
val input = editText.text.toString().trim()
|
||||
if (tab == null) {
|
||||
val newTab = createTabDataFromId(HASHTAG, listOf(input))
|
||||
currentTabs.add(newTab)
|
||||
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
||||
} else {
|
||||
val newTab = tab.copy(arguments = tab.arguments + input)
|
||||
currentTabs[tabPosition] = newTab
|
||||
|
||||
currentTabsAdapter.notifyItemChanged(tabPosition)
|
||||
}
|
||||
|
||||
updateAvailableTabs()
|
||||
saveTabs()
|
||||
currentTabsAdapter.notifyItemChanged(tabPosition)
|
||||
}
|
||||
.create()
|
||||
|
||||
updateAvailableTabs()
|
||||
saveTabs()
|
||||
}
|
||||
.create()
|
||||
|
||||
editText.onTextChanged { s, _, _, _ ->
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = validateHashtag(s)
|
||||
|
@ -254,28 +254,28 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
private fun showSelectListDialog() {
|
||||
val adapter = ListSelectionAdapter(this)
|
||||
mastodonApi.getLists()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe (
|
||||
{ lists ->
|
||||
adapter.addAll(lists)
|
||||
},
|
||||
{ throwable ->
|
||||
Log.e("TabPreferenceActivity", "failed to load lists", throwable)
|
||||
}
|
||||
)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe(
|
||||
{ lists ->
|
||||
adapter.addAll(lists)
|
||||
},
|
||||
{ throwable ->
|
||||
Log.e("TabPreferenceActivity", "failed to load lists", throwable)
|
||||
}
|
||||
)
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.select_list_title)
|
||||
.setAdapter(adapter) { _, position ->
|
||||
val list = adapter.getItem(position)
|
||||
val newTab = createTabDataFromId(LIST, listOf(list!!.id, list.title))
|
||||
currentTabs.add(newTab)
|
||||
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
||||
updateAvailableTabs()
|
||||
saveTabs()
|
||||
}
|
||||
.show()
|
||||
.setTitle(R.string.select_list_title)
|
||||
.setAdapter(adapter) { _, position ->
|
||||
val list = adapter.getItem(position)
|
||||
val newTab = createTabDataFromId(LIST, listOf(list!!.id, list.title))
|
||||
currentTabs.add(newTab)
|
||||
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
||||
updateAvailableTabs()
|
||||
saveTabs()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun validateHashtag(input: CharSequence?): Boolean {
|
||||
|
@ -330,10 +330,9 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
it.tabPreferences = currentTabs
|
||||
accountManager.saveAccount(it)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe()
|
||||
|
||||
.subscribeOn(Schedulers.io())
|
||||
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe()
|
||||
}
|
||||
tabsChanged = true
|
||||
}
|
||||
|
@ -357,5 +356,4 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
private const val MIN_TAB_COUNT = 2
|
||||
private const val MAX_TAB_COUNT = 5
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -68,8 +68,8 @@ class TuskyApplication : Application(), HasAndroidInjector {
|
|||
// init the custom emoji fonts
|
||||
val emojiSelection = preferences.getInt(PrefKeys.EMOJI, 0)
|
||||
val emojiConfig = EmojiCompatFont.byId(emojiSelection)
|
||||
.getConfig(this)
|
||||
.setReplaceAll(true)
|
||||
.getConfig(this)
|
||||
.setReplaceAll(true)
|
||||
EmojiCompat.init(emojiConfig)
|
||||
|
||||
// init night mode
|
||||
|
@ -81,10 +81,10 @@ class TuskyApplication : Application(), HasAndroidInjector {
|
|||
}
|
||||
|
||||
WorkManager.initialize(
|
||||
this,
|
||||
androidx.work.Configuration.Builder()
|
||||
.setWorkerFactory(notificationWorkerFactory)
|
||||
.build()
|
||||
this,
|
||||
androidx.work.Configuration.Builder()
|
||||
.setWorkerFactory(notificationWorkerFactory)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -104,4 +104,4 @@ class TuskyApplication : Application(), HasAndroidInjector {
|
|||
@JvmStatic
|
||||
lateinit var localeManager: LocaleManager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ import java.io.File
|
|||
import java.io.FileNotFoundException
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
typealias ToolbarVisibilityListener = (isVisible: Boolean) -> Unit
|
||||
|
||||
|
@ -102,17 +102,16 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
val realAttachs = attachments!!.map(AttachmentViewData::attachment)
|
||||
// Setup the view pager.
|
||||
ImagePagerAdapter(this, realAttachs, initialPosition)
|
||||
|
||||
} else {
|
||||
imageUrl = intent.getStringExtra(EXTRA_SINGLE_IMAGE_URL)
|
||||
?: throw IllegalArgumentException("attachment list or image url has to be set")
|
||||
?: throw IllegalArgumentException("attachment list or image url has to be set")
|
||||
|
||||
SingleImagePagerAdapter(this, imageUrl!!)
|
||||
}
|
||||
|
||||
binding.viewPager.adapter = adapter
|
||||
binding.viewPager.setCurrentItem(initialPosition, false)
|
||||
binding.viewPager.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
|
||||
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
binding.toolbar.title = getPageTitle(position)
|
||||
}
|
||||
|
@ -183,17 +182,17 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
}
|
||||
|
||||
binding.toolbar.animate().alpha(alpha)
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
binding.toolbar.visibility = visibility
|
||||
animation.removeListener(this)
|
||||
}
|
||||
})
|
||||
.start()
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
binding.toolbar.visibility = visibility
|
||||
animation.removeListener(this)
|
||||
}
|
||||
})
|
||||
.start()
|
||||
}
|
||||
|
||||
private fun getPageTitle(position: Int): CharSequence {
|
||||
if(attachments == null) {
|
||||
if (attachments == null) {
|
||||
return ""
|
||||
}
|
||||
return String.format(Locale.getDefault(), "%d/%d", position + 1, attachments?.size)
|
||||
|
@ -206,8 +205,10 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
|
||||
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
val request = DownloadManager.Request(Uri.parse(url))
|
||||
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES,
|
||||
getString(R.string.app_name) + "/" + filename)
|
||||
request.setDestinationInExternalPublicDir(
|
||||
Environment.DIRECTORY_PICTURES,
|
||||
getString(R.string.app_name) + "/" + filename
|
||||
)
|
||||
downloadManager.enqueue(request)
|
||||
}
|
||||
|
||||
|
@ -261,7 +262,6 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_media_to)))
|
||||
}
|
||||
|
||||
|
||||
private var isCreating: Boolean = false
|
||||
|
||||
private fun shareImage(directory: File, url: String) {
|
||||
|
@ -270,7 +270,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
invalidateOptionsMenu()
|
||||
val file = File(directory, getTemporaryMediaFilename("png"))
|
||||
val futureTask: FutureTarget<Bitmap> =
|
||||
Glide.with(applicationContext).asBitmap().load(Uri.parse(url)).submit()
|
||||
Glide.with(applicationContext).asBitmap().load(Uri.parse(url)).submit()
|
||||
Single.fromCallable {
|
||||
val bitmap = futureTask.get()
|
||||
try {
|
||||
|
@ -284,32 +284,30 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
Log.e(TAG, "Error writing temporary media.")
|
||||
}
|
||||
return@fromCallable false
|
||||
|
||||
}
|
||||
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnDispose {
|
||||
futureTask.cancel(true)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnDispose {
|
||||
futureTask.cancel(true)
|
||||
}
|
||||
.autoDispose(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe(
|
||||
{ result ->
|
||||
Log.d(TAG, "Download image result: $result")
|
||||
isCreating = false
|
||||
invalidateOptionsMenu()
|
||||
binding.progressBarShare.visibility = View.GONE
|
||||
if (result)
|
||||
shareFile(file, "image/png")
|
||||
},
|
||||
{ error ->
|
||||
isCreating = false
|
||||
invalidateOptionsMenu()
|
||||
binding.progressBarShare.visibility = View.GONE
|
||||
Log.e(TAG, "Failed to download image", error)
|
||||
}
|
||||
.autoDispose(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe(
|
||||
{ result ->
|
||||
Log.d(TAG, "Download image result: $result")
|
||||
isCreating = false
|
||||
invalidateOptionsMenu()
|
||||
binding.progressBarShare.visibility = View.GONE
|
||||
if (result)
|
||||
shareFile(file, "image/png")
|
||||
},
|
||||
{ error ->
|
||||
isCreating = false
|
||||
invalidateOptionsMenu()
|
||||
binding.progressBarShare.visibility = View.GONE
|
||||
Log.e(TAG, "Failed to download image", error)
|
||||
}
|
||||
)
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
private fun shareMediaFile(directory: File, url: String) {
|
||||
|
@ -352,7 +350,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
}
|
||||
}
|
||||
|
||||
abstract class ViewMediaAdapter(activity: FragmentActivity): FragmentStateAdapter(activity) {
|
||||
abstract class ViewMediaAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
|
||||
abstract fun onTransitionEnd(position: Int)
|
||||
}
|
||||
|
||||
|
|
|
@ -121,5 +121,4 @@ abstract class AccountAdapter<AVH : RecyclerView.ViewHolder> internal constructo
|
|||
const val VIEW_TYPE_ACCOUNT = 0
|
||||
const val VIEW_TYPE_FOOTER = 1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,20 +16,23 @@
|
|||
package com.keylesspalace.tusky.adapter
|
||||
|
||||
import android.text.method.LinkMovementMethod
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.ItemAccountFieldBinding
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.Field
|
||||
import com.keylesspalace.tusky.entity.IdentityProof
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
import com.keylesspalace.tusky.util.Either
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
|
||||
class AccountFieldAdapter(
|
||||
private val linkListener: LinkListener,
|
||||
private val animateEmojis: Boolean
|
||||
private val linkListener: LinkListener,
|
||||
private val animateEmojis: Boolean
|
||||
) : RecyclerView.Adapter<BindingHolder<ItemAccountFieldBinding>>() {
|
||||
|
||||
var emojis: List<Emoji> = emptyList()
|
||||
|
@ -47,7 +50,7 @@ class AccountFieldAdapter(
|
|||
val nameTextView = holder.binding.accountFieldName
|
||||
val valueTextView = holder.binding.accountFieldValue
|
||||
|
||||
if(proofOrField.isLeft()) {
|
||||
if (proofOrField.isLeft()) {
|
||||
val identityProof = proofOrField.asLeft()
|
||||
|
||||
nameTextView.text = identityProof.provider
|
||||
|
@ -55,7 +58,7 @@ class AccountFieldAdapter(
|
|||
|
||||
valueTextView.movementMethod = LinkMovementMethod.getInstance()
|
||||
|
||||
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
|
||||
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
|
||||
} else {
|
||||
val field = proofOrField.asRight()
|
||||
val emojifiedName = field.name.emojify(emojis, nameTextView, animateEmojis)
|
||||
|
@ -64,12 +67,11 @@ class AccountFieldAdapter(
|
|||
val emojifiedValue = field.value.emojify(emojis, valueTextView, animateEmojis)
|
||||
LinkHelper.setClickableText(valueTextView, emojifiedValue, null, linkListener)
|
||||
|
||||
if(field.verifiedAt != null) {
|
||||
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
|
||||
if (field.verifiedAt != null) {
|
||||
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
|
||||
} else {
|
||||
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0 )
|
||||
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<BindingHolder<ItemEditField
|
|||
fields.forEach { field ->
|
||||
fieldData.add(MutableStringPair(field.name, field.value))
|
||||
}
|
||||
if(fieldData.isEmpty()) {
|
||||
if (fieldData.isEmpty()) {
|
||||
fieldData.add(MutableStringPair("", ""))
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<BindingHolder<ItemEditField
|
|||
holder.binding.accountFieldName.setText(fieldData[position].first)
|
||||
holder.binding.accountFieldValue.setText(fieldData[position].second)
|
||||
|
||||
holder.binding.accountFieldName.addTextChangedListener(object: TextWatcher {
|
||||
holder.binding.accountFieldName.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(newText: Editable) {
|
||||
fieldData[holder.bindingAdapterPosition].first = newText.toString()
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<BindingHolder<ItemEditField
|
|||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||
})
|
||||
|
||||
holder.binding.accountFieldValue.addTextChangedListener(object: TextWatcher {
|
||||
holder.binding.accountFieldValue.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(newText: Editable) {
|
||||
fieldData[holder.bindingAdapterPosition].second = newText.toString()
|
||||
}
|
||||
|
@ -82,9 +82,7 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<BindingHolder<ItemEditField
|
|||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
class MutableStringPair (var first: String, var second: String)
|
||||
|
||||
class MutableStringPair(var first: String, var second: String)
|
||||
}
|
||||
|
|
|
@ -25,7 +25,8 @@ import com.keylesspalace.tusky.R
|
|||
import com.keylesspalace.tusky.databinding.ItemAutocompleteAccountBinding
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import com.keylesspalace.tusky.util.loadAvatar
|
||||
|
||||
class AccountSelectionAdapter(context: Context) : ArrayAdapter<AccountEntity>(context, R.layout.item_autocomplete_account) {
|
||||
|
||||
|
@ -48,9 +49,8 @@ class AccountSelectionAdapter(context: Context) : ArrayAdapter<AccountEntity>(co
|
|||
val animateAvatar = pm.getBoolean("animateGifAvatars", false)
|
||||
|
||||
loadAvatar(account.profilePictureUrl, binding.avatar, avatarRadius, animateAvatar)
|
||||
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,4 +77,4 @@ class BlocksAdapter(
|
|||
itemView.setOnClickListener { listener.onViewAccount(id) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,15 +22,15 @@ import com.bumptech.glide.Glide
|
|||
import com.keylesspalace.tusky.databinding.ItemEmojiButtonBinding
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
class EmojiAdapter(
|
||||
emojiList: List<Emoji>,
|
||||
private val onEmojiSelectedListener: OnEmojiSelectedListener
|
||||
emojiList: List<Emoji>,
|
||||
private val onEmojiSelectedListener: OnEmojiSelectedListener
|
||||
) : RecyclerView.Adapter<BindingHolder<ItemEmojiButtonBinding>>() {
|
||||
|
||||
private val emojiList : List<Emoji> = emojiList.filter { emoji -> emoji.visibleInPicker == null || emoji.visibleInPicker }
|
||||
.sortedBy { it.shortcode.lowercase(Locale.ROOT) }
|
||||
private val emojiList: List<Emoji> = emojiList.filter { emoji -> emoji.visibleInPicker == null || emoji.visibleInPicker }
|
||||
.sortedBy { it.shortcode.lowercase(Locale.ROOT) }
|
||||
|
||||
override fun getItemCount() = emojiList.size
|
||||
|
||||
|
@ -44,8 +44,8 @@ class EmojiAdapter(
|
|||
val emojiImageView = holder.binding.root
|
||||
|
||||
Glide.with(emojiImageView)
|
||||
.load(emoji.url)
|
||||
.into(emojiImageView)
|
||||
.load(emoji.url)
|
||||
.into(emojiImageView)
|
||||
|
||||
emojiImageView.setOnClickListener {
|
||||
onEmojiSelectedListener.onEmojiSelected(emoji.shortcode)
|
||||
|
|
|
@ -16,7 +16,6 @@ package com.keylesspalace.tusky.adapter
|
|||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.interfaces.AccountActionListener
|
||||
|
||||
|
@ -36,4 +35,4 @@ class FollowAdapter(
|
|||
viewHolder.setupWithAccount(accountList[position], animateAvatar, animateEmojis)
|
||||
viewHolder.setupActionListener(accountActionListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,11 +24,14 @@ import com.keylesspalace.tusky.R
|
|||
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.interfaces.AccountActionListener
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import com.keylesspalace.tusky.util.loadAvatar
|
||||
import com.keylesspalace.tusky.util.unicodeWrap
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
|
||||
class FollowRequestViewHolder(
|
||||
private val binding: ItemFollowRequestBinding,
|
||||
private val showHeader: Boolean
|
||||
private val binding: ItemFollowRequestBinding,
|
||||
private val showHeader: Boolean
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun setupWithAccount(account: Account, animateAvatar: Boolean, animateEmojis: Boolean) {
|
||||
|
|
|
@ -16,8 +16,6 @@ package com.keylesspalace.tusky.adapter
|
|||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding
|
||||
import com.keylesspalace.tusky.interfaces.AccountActionListener
|
||||
|
||||
|
@ -38,4 +36,4 @@ class FollowRequestsAdapter(
|
|||
viewHolder.setupWithAccount(accountList[position], animateAvatar, animateEmojis)
|
||||
viewHolder.setupActionListener(accountActionListener, accountList[position].id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ class FollowRequestsHeaderAdapter(private val instanceName: String, private val
|
|||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_follow_requests_header, parent, false) as TextView
|
||||
.inflate(R.layout.item_follow_requests_header, parent, false) as TextView
|
||||
return HeaderViewHolder(view)
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,6 @@ class FollowRequestsHeaderAdapter(private val instanceName: String, private val
|
|||
}
|
||||
|
||||
override fun getItemCount() = if (accountLocked) 0 else 1
|
||||
|
||||
}
|
||||
|
||||
class HeaderViewHolder(var textView: TextView) : RecyclerView.ViewHolder(textView)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
package com.keylesspalace.tusky.adapter
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class LoadingFooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
class LoadingFooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
|
|
|
@ -13,7 +13,7 @@ import com.keylesspalace.tusky.entity.Account
|
|||
import com.keylesspalace.tusky.interfaces.AccountActionListener
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import com.keylesspalace.tusky.util.loadAvatar
|
||||
import java.util.*
|
||||
import java.util.HashMap
|
||||
|
||||
/**
|
||||
* Displays a list of muted accounts with mute/unmute account and mute/unmute notifications
|
||||
|
@ -129,4 +129,4 @@ class MutesAdapter(
|
|||
itemView.setOnClickListener { listener.onViewAccount(id) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,10 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.keylesspalace.tusky.databinding.ItemNetworkStateBinding
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
|
||||
class NetworkStateViewHolder(private val binding: ItemNetworkStateBinding,
|
||||
private val retryCallback: () -> Unit)
|
||||
: RecyclerView.ViewHolder(binding.root) {
|
||||
class NetworkStateViewHolder(
|
||||
private val binding: ItemNetworkStateBinding,
|
||||
private val retryCallback: () -> Unit
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun setUpWithNetworkState(state: LoadState) {
|
||||
binding.progressBar.visible(state == LoadState.Loading)
|
||||
|
@ -38,5 +39,4 @@ class NetworkStateViewHolder(private val binding: ItemNetworkStateBinding,
|
|||
retryCallback()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,4 +38,4 @@ class PlaceholderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
|||
listener.onLoadMore(bindingAdapterPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import com.keylesspalace.tusky.viewdata.PollOptionViewData
|
|||
import com.keylesspalace.tusky.viewdata.buildDescription
|
||||
import com.keylesspalace.tusky.viewdata.calculatePercent
|
||||
|
||||
class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||
class PollAdapter : RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||
|
||||
private var pollOptions: List<PollOptionViewData> = emptyList()
|
||||
private var voteCount: Int = 0
|
||||
|
@ -40,13 +40,14 @@ class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
|||
private var animateEmojis = false
|
||||
|
||||
fun setup(
|
||||
options: List<PollOptionViewData>,
|
||||
voteCount: Int,
|
||||
votersCount: Int?,
|
||||
emojis: List<Emoji>,
|
||||
mode: Int,
|
||||
resultClickListener: View.OnClickListener?,
|
||||
animateEmojis: Boolean) {
|
||||
options: List<PollOptionViewData>,
|
||||
voteCount: Int,
|
||||
votersCount: Int?,
|
||||
emojis: List<Emoji>,
|
||||
mode: Int,
|
||||
resultClickListener: View.OnClickListener?,
|
||||
animateEmojis: Boolean
|
||||
) {
|
||||
this.pollOptions = options
|
||||
this.voteCount = voteCount
|
||||
this.votersCount = votersCount
|
||||
|
@ -57,12 +58,11 @@ class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
|||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun getSelected() : List<Int> {
|
||||
fun getSelected(): List<Int> {
|
||||
return pollOptions.filter { it.selected }
|
||||
.map { pollOptions.indexOf(it) }
|
||||
.map { pollOptions.indexOf(it) }
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemPollBinding> {
|
||||
val binding = ItemPollBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return BindingHolder(binding)
|
||||
|
@ -82,12 +82,12 @@ class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
|||
radioButton.visible(mode == SINGLE)
|
||||
checkBox.visible(mode == MULTIPLE)
|
||||
|
||||
when(mode) {
|
||||
when (mode) {
|
||||
RESULT -> {
|
||||
val percent = calculatePercent(option.votesCount, votersCount, voteCount)
|
||||
val emojifiedPollOptionText = buildDescription(option.title, percent, resultTextView.context)
|
||||
.emojify(emojis, resultTextView, animateEmojis)
|
||||
resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText)
|
||||
.emojify(emojis, resultTextView, animateEmojis)
|
||||
resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText)
|
||||
|
||||
val level = percent * 100
|
||||
|
||||
|
@ -114,7 +114,6 @@ class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -23,7 +23,7 @@ import androidx.core.widget.TextViewCompat
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
|
||||
class PreviewPollOptionsAdapter: RecyclerView.Adapter<PreviewViewHolder>() {
|
||||
class PreviewPollOptionsAdapter : RecyclerView.Adapter<PreviewViewHolder>() {
|
||||
|
||||
private var options: List<String> = emptyList()
|
||||
private var multiple: Boolean = false
|
||||
|
@ -60,7 +60,6 @@ class PreviewPollOptionsAdapter: RecyclerView.Adapter<PreviewViewHolder>() {
|
|||
|
||||
textView.setOnClickListener(clickListener)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PreviewViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
|
||||
class PreviewViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
|
|
|
@ -43,10 +43,11 @@ interface ItemInteractionListener {
|
|||
fun onChipClicked(tab: TabData, tabPosition: Int, chipPosition: Int)
|
||||
}
|
||||
|
||||
class TabAdapter(private var data: List<TabData>,
|
||||
private val small: Boolean,
|
||||
private val listener: ItemInteractionListener,
|
||||
private var removeButtonEnabled: Boolean = false
|
||||
class TabAdapter(
|
||||
private var data: List<TabData>,
|
||||
private val small: Boolean,
|
||||
private val listener: ItemInteractionListener,
|
||||
private var removeButtonEnabled: Boolean = false
|
||||
) : RecyclerView.Adapter<BindingHolder<ViewBinding>>() {
|
||||
|
||||
fun updateData(newData: List<TabData>) {
|
||||
|
@ -77,7 +78,6 @@ class TabAdapter(private var data: List<TabData>,
|
|||
binding.textView.setOnClickListener {
|
||||
listener.onTabAdded(tab)
|
||||
}
|
||||
|
||||
} else {
|
||||
val binding = holder.binding as ItemTabPreferenceBinding
|
||||
|
||||
|
@ -102,9 +102,9 @@ class TabAdapter(private var data: List<TabData>,
|
|||
}
|
||||
binding.removeButton.isEnabled = removeButtonEnabled
|
||||
ThemeUtils.setDrawableTint(
|
||||
holder.itemView.context,
|
||||
binding.removeButton.drawable,
|
||||
(if (removeButtonEnabled) android.R.attr.textColorTertiary else R.attr.textColorDisabled)
|
||||
holder.itemView.context,
|
||||
binding.removeButton.drawable,
|
||||
(if (removeButtonEnabled) android.R.attr.textColorTertiary else R.attr.textColorDisabled)
|
||||
)
|
||||
|
||||
if (tab.id == HASHTAG) {
|
||||
|
@ -118,14 +118,14 @@ class TabAdapter(private var data: List<TabData>,
|
|||
tab.arguments.forEachIndexed { i, arg ->
|
||||
|
||||
val chip = binding.chipGroup.getChildAt(i).takeUnless { it.id == R.id.actionChip } as Chip?
|
||||
?: Chip(context).apply {
|
||||
binding.chipGroup.addView(this, binding.chipGroup.size - 1)
|
||||
chipIconTint = ColorStateList.valueOf(ThemeUtils.getColor(context, android.R.attr.textColorPrimary))
|
||||
}
|
||||
?: Chip(context).apply {
|
||||
binding.chipGroup.addView(this, binding.chipGroup.size - 1)
|
||||
chipIconTint = ColorStateList.valueOf(ThemeUtils.getColor(context, android.R.attr.textColorPrimary))
|
||||
}
|
||||
|
||||
chip.text = arg
|
||||
|
||||
if(tab.arguments.size <= 1) {
|
||||
if (tab.arguments.size <= 1) {
|
||||
chip.chipIcon = null
|
||||
chip.setOnClickListener(null)
|
||||
} else {
|
||||
|
@ -136,14 +136,13 @@ class TabAdapter(private var data: List<TabData>,
|
|||
}
|
||||
}
|
||||
|
||||
while(binding.chipGroup.size - 1 > tab.arguments.size) {
|
||||
while (binding.chipGroup.size - 1 > tab.arguments.size) {
|
||||
binding.chipGroup.removeViewAt(tab.arguments.size)
|
||||
}
|
||||
|
||||
binding.actionChip.setOnClickListener {
|
||||
listener.onActionChipClicked(tab, holder.bindingAdapterPosition)
|
||||
}
|
||||
|
||||
} else {
|
||||
binding.chipGroup.hide()
|
||||
}
|
||||
|
|
|
@ -111,8 +111,8 @@ class ThreadAdapter(
|
|||
fun getItem(position: Int): StatusViewData.Concrete? = statuses.getOrNull(position)
|
||||
|
||||
fun setDetailedStatusPosition(position: Int) {
|
||||
if (position != detailedStatusPosition
|
||||
&& detailedStatusPosition != RecyclerView.NO_POSITION
|
||||
if (position != detailedStatusPosition &&
|
||||
detailedStatusPosition != RecyclerView.NO_POSITION
|
||||
) {
|
||||
val prior = detailedStatusPosition
|
||||
detailedStatusPosition = position
|
||||
|
@ -126,4 +126,4 @@ class ThreadAdapter(
|
|||
private const val VIEW_TYPE_STATUS = 0
|
||||
private const val VIEW_TYPE_STATUS_DETAILED = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ import io.reactivex.rxjava3.schedulers.Schedulers
|
|||
import javax.inject.Inject
|
||||
|
||||
class CacheUpdater @Inject constructor(
|
||||
eventHub: EventHub,
|
||||
accountManager: AccountManager,
|
||||
private val appDatabase: AppDatabase,
|
||||
gson: Gson
|
||||
eventHub: EventHub,
|
||||
accountManager: AccountManager,
|
||||
private val appDatabase: AppDatabase,
|
||||
gson: Gson
|
||||
) {
|
||||
|
||||
private val disposable: Disposable
|
||||
|
@ -27,7 +27,7 @@ class CacheUpdater @Inject constructor(
|
|||
is ReblogEvent ->
|
||||
timelineDao.setReblogged(accountId, event.statusId, event.reblog)
|
||||
is BookmarkEvent ->
|
||||
timelineDao.setBookmarked(accountId, event.statusId, event.bookmark )
|
||||
timelineDao.setBookmarked(accountId, event.statusId, event.bookmark)
|
||||
is UnfollowEvent ->
|
||||
timelineDao.removeAllByUser(accountId, event.accountId)
|
||||
is StatusDeletedEvent ->
|
||||
|
@ -49,7 +49,7 @@ class CacheUpdater @Inject constructor(
|
|||
appDatabase.timelineDao().removeAllForAccount(accountId)
|
||||
appDatabase.timelineDao().removeAllUsersForAccount(accountId)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,6 @@ data class ProfileEditedEvent(val newProfileData: Account) : Dispatchable
|
|||
data class PreferenceChangedEvent(val preferenceKey: String) : Dispatchable
|
||||
data class MainTabsChangedEvent(val newTabs: List<TabData>) : Dispatchable
|
||||
data class PollVoteEvent(val statusId: String, val poll: Poll) : Dispatchable
|
||||
data class DomainMuteEvent(val instance: String): Dispatchable
|
||||
data class AnnouncementReadEvent(val announcementId: String): Dispatchable
|
||||
data class PinEvent(val statusId: String, val pinned: Boolean): Dispatchable
|
||||
data class DomainMuteEvent(val instance: String) : Dispatchable
|
||||
data class AnnouncementReadEvent(val announcementId: String) : Dispatchable
|
||||
data class PinEvent(val statusId: String, val pinned: Boolean) : Dispatchable
|
||||
|
|
|
@ -19,4 +19,4 @@ object EventHubImpl : EventHub {
|
|||
override fun dispatch(event: Dispatchable) {
|
||||
eventsSubject.onNext(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,17 +31,17 @@ import com.keylesspalace.tusky.util.BindingHolder
|
|||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
|
||||
interface AnnouncementActionListener: LinkListener {
|
||||
interface AnnouncementActionListener : LinkListener {
|
||||
fun openReactionPicker(announcementId: String, target: View)
|
||||
fun addReaction(announcementId: String, name: String)
|
||||
fun removeReaction(announcementId: String, name: String)
|
||||
}
|
||||
|
||||
class AnnouncementAdapter(
|
||||
private var items: List<Announcement> = emptyList(),
|
||||
private val listener: AnnouncementActionListener,
|
||||
private val wellbeingEnabled: Boolean = false,
|
||||
private val animateEmojis: Boolean = false
|
||||
private var items: List<Announcement> = emptyList(),
|
||||
private val listener: AnnouncementActionListener,
|
||||
private val wellbeingEnabled: Boolean = false,
|
||||
private val animateEmojis: Boolean = false
|
||||
) : RecyclerView.Adapter<BindingHolder<ItemAnnouncementBinding>>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemAnnouncementBinding> {
|
||||
|
@ -67,12 +67,12 @@ class AnnouncementAdapter(
|
|||
}
|
||||
|
||||
item.reactions.forEachIndexed { i, reaction ->
|
||||
(chips.getChildAt(i)?.takeUnless { it.id == R.id.addReactionChip } as Chip?
|
||||
?: Chip(ContextThemeWrapper(chips.context, R.style.Widget_MaterialComponents_Chip_Choice)).apply {
|
||||
isCheckable = true
|
||||
checkedIcon = null
|
||||
chips.addView(this, i)
|
||||
})
|
||||
chips.getChildAt(i)?.takeUnless { it.id == R.id.addReactionChip } as Chip?
|
||||
?: Chip(ContextThemeWrapper(chips.context, R.style.Widget_MaterialComponents_Chip_Choice)).apply {
|
||||
isCheckable = true
|
||||
checkedIcon = null
|
||||
chips.addView(this, i)
|
||||
}
|
||||
.apply {
|
||||
val emojiText = if (reaction.url == null) {
|
||||
reaction.name
|
||||
|
@ -80,16 +80,18 @@ class AnnouncementAdapter(
|
|||
context.getString(R.string.emoji_shortcode_format, reaction.name)
|
||||
}
|
||||
this.text = ("$emojiText ${reaction.count}")
|
||||
.emojify(
|
||||
listOf(Emoji(
|
||||
reaction.name,
|
||||
reaction.url ?: "",
|
||||
reaction.staticUrl ?: "",
|
||||
null
|
||||
)),
|
||||
this,
|
||||
animateEmojis
|
||||
)
|
||||
.emojify(
|
||||
listOf(
|
||||
Emoji(
|
||||
reaction.name,
|
||||
reaction.url ?: "",
|
||||
reaction.staticUrl ?: "",
|
||||
null
|
||||
)
|
||||
),
|
||||
this,
|
||||
animateEmojis
|
||||
)
|
||||
|
||||
isChecked = reaction.me
|
||||
|
||||
|
|
|
@ -34,7 +34,12 @@ import com.keylesspalace.tusky.databinding.ActivityAnnouncementsBinding
|
|||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.Error
|
||||
import com.keylesspalace.tusky.util.Loading
|
||||
import com.keylesspalace.tusky.util.Success
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.view.EmojiPicker
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -52,13 +57,13 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener,
|
|||
private val picker by lazy { EmojiPicker(this) }
|
||||
private val pickerDialog by lazy {
|
||||
PopupWindow(this)
|
||||
.apply {
|
||||
contentView = picker
|
||||
isFocusable = true
|
||||
setOnDismissListener {
|
||||
currentAnnouncementId = null
|
||||
}
|
||||
.apply {
|
||||
contentView = picker
|
||||
isFocusable = true
|
||||
setOnDismissListener {
|
||||
currentAnnouncementId = null
|
||||
}
|
||||
}
|
||||
}
|
||||
private var currentAnnouncementId: String? = null
|
||||
|
||||
|
|
|
@ -27,15 +27,20 @@ import com.keylesspalace.tusky.entity.Announcement
|
|||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.Instance
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.Either
|
||||
import com.keylesspalace.tusky.util.Error
|
||||
import com.keylesspalace.tusky.util.Loading
|
||||
import com.keylesspalace.tusky.util.Resource
|
||||
import com.keylesspalace.tusky.util.RxAwareViewModel
|
||||
import com.keylesspalace.tusky.util.Success
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import javax.inject.Inject
|
||||
|
||||
class AnnouncementsViewModel @Inject constructor(
|
||||
accountManager: AccountManager,
|
||||
private val appDatabase: AppDatabase,
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val eventHub: EventHub
|
||||
accountManager: AccountManager,
|
||||
private val appDatabase: AppDatabase,
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val eventHub: EventHub
|
||||
) : RxAwareViewModel() {
|
||||
|
||||
private val announcementsMutable = MutableLiveData<Resource<List<Announcement>>>()
|
||||
|
@ -45,139 +50,153 @@ class AnnouncementsViewModel @Inject constructor(
|
|||
val emojis: LiveData<List<Emoji>> = emojisMutable
|
||||
|
||||
init {
|
||||
Single.zip(mastodonApi.getCustomEmojis(),
|
||||
appDatabase.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!)
|
||||
.map<Either<InstanceEntity, Instance>> { Either.Left(it) }
|
||||
.onErrorResumeNext {
|
||||
mastodonApi.getInstance()
|
||||
.map { Either.Right(it) }
|
||||
},
|
||||
{ emojis, either ->
|
||||
either.asLeftOrNull()?.copy(emojiList = emojis)
|
||||
?: InstanceEntity(
|
||||
accountManager.activeAccount?.domain!!,
|
||||
emojis,
|
||||
either.asRight().maxTootChars,
|
||||
either.asRight().pollLimits?.maxOptions,
|
||||
either.asRight().pollLimits?.maxOptionChars,
|
||||
either.asRight().version
|
||||
)
|
||||
})
|
||||
.doOnSuccess {
|
||||
appDatabase.instanceDao().insertOrReplace(it)
|
||||
}
|
||||
.subscribe({
|
||||
Single.zip(
|
||||
mastodonApi.getCustomEmojis(),
|
||||
appDatabase.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!)
|
||||
.map<Either<InstanceEntity, Instance>> { Either.Left(it) }
|
||||
.onErrorResumeNext {
|
||||
mastodonApi.getInstance()
|
||||
.map { Either.Right(it) }
|
||||
},
|
||||
{ emojis, either ->
|
||||
either.asLeftOrNull()?.copy(emojiList = emojis)
|
||||
?: InstanceEntity(
|
||||
accountManager.activeAccount?.domain!!,
|
||||
emojis,
|
||||
either.asRight().maxTootChars,
|
||||
either.asRight().pollLimits?.maxOptions,
|
||||
either.asRight().pollLimits?.maxOptionChars,
|
||||
either.asRight().version
|
||||
)
|
||||
}
|
||||
)
|
||||
.doOnSuccess {
|
||||
appDatabase.instanceDao().insertOrReplace(it)
|
||||
}
|
||||
.subscribe(
|
||||
{
|
||||
emojisMutable.postValue(it.emojiList.orEmpty())
|
||||
}, {
|
||||
},
|
||||
{
|
||||
Log.w(TAG, "Failed to get custom emojis.", it)
|
||||
})
|
||||
.autoDispose()
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
}
|
||||
|
||||
fun load() {
|
||||
announcementsMutable.postValue(Loading())
|
||||
mastodonApi.listAnnouncements()
|
||||
.subscribe({
|
||||
.subscribe(
|
||||
{
|
||||
announcementsMutable.postValue(Success(it))
|
||||
it.filter { announcement -> !announcement.read }
|
||||
.forEach { announcement ->
|
||||
mastodonApi.dismissAnnouncement(announcement.id)
|
||||
.subscribe(
|
||||
{
|
||||
eventHub.dispatch(AnnouncementReadEvent(announcement.id))
|
||||
},
|
||||
{ throwable ->
|
||||
Log.d(TAG, "Failed to mark announcement as read.", throwable)
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
}
|
||||
}, {
|
||||
.forEach { announcement ->
|
||||
mastodonApi.dismissAnnouncement(announcement.id)
|
||||
.subscribe(
|
||||
{
|
||||
eventHub.dispatch(AnnouncementReadEvent(announcement.id))
|
||||
},
|
||||
{ throwable ->
|
||||
Log.d(TAG, "Failed to mark announcement as read.", throwable)
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
}
|
||||
},
|
||||
{
|
||||
announcementsMutable.postValue(Error(cause = it))
|
||||
})
|
||||
.autoDispose()
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
}
|
||||
|
||||
fun addReaction(announcementId: String, name: String) {
|
||||
mastodonApi.addAnnouncementReaction(announcementId, name)
|
||||
.subscribe({
|
||||
.subscribe(
|
||||
{
|
||||
announcementsMutable.postValue(
|
||||
Success(
|
||||
announcements.value!!.data!!.map { announcement ->
|
||||
if (announcement.id == announcementId) {
|
||||
announcement.copy(
|
||||
reactions = if (announcement.reactions.find { reaction -> reaction.name == name } != null) {
|
||||
announcement.reactions.map { reaction ->
|
||||
if (reaction.name == name) {
|
||||
reaction.copy(
|
||||
count = reaction.count + 1,
|
||||
me = true
|
||||
)
|
||||
} else {
|
||||
reaction
|
||||
}
|
||||
}
|
||||
} else {
|
||||
listOf(
|
||||
*announcement.reactions.toTypedArray(),
|
||||
emojis.value!!.find { emoji -> emoji.shortcode == name }
|
||||
!!.run {
|
||||
Announcement.Reaction(
|
||||
name,
|
||||
1,
|
||||
true,
|
||||
url,
|
||||
staticUrl
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
Success(
|
||||
announcements.value!!.data!!.map { announcement ->
|
||||
if (announcement.id == announcementId) {
|
||||
announcement.copy(
|
||||
reactions = if (announcement.reactions.find { reaction -> reaction.name == name } != null) {
|
||||
announcement.reactions.map { reaction ->
|
||||
if (reaction.name == name) {
|
||||
reaction.copy(
|
||||
count = reaction.count + 1,
|
||||
me = true
|
||||
)
|
||||
} else {
|
||||
reaction
|
||||
}
|
||||
}
|
||||
} else {
|
||||
announcement
|
||||
listOf(
|
||||
*announcement.reactions.toTypedArray(),
|
||||
emojis.value!!.find { emoji -> emoji.shortcode == name }
|
||||
!!.run {
|
||||
Announcement.Reaction(
|
||||
name,
|
||||
1,
|
||||
true,
|
||||
url,
|
||||
staticUrl
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
} else {
|
||||
announcement
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}, {
|
||||
},
|
||||
{
|
||||
Log.w(TAG, "Failed to add reaction to the announcement.", it)
|
||||
})
|
||||
.autoDispose()
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
}
|
||||
|
||||
fun removeReaction(announcementId: String, name: String) {
|
||||
mastodonApi.removeAnnouncementReaction(announcementId, name)
|
||||
.subscribe({
|
||||
.subscribe(
|
||||
{
|
||||
announcementsMutable.postValue(
|
||||
Success(
|
||||
announcements.value!!.data!!.map { announcement ->
|
||||
if (announcement.id == announcementId) {
|
||||
announcement.copy(
|
||||
reactions = announcement.reactions.mapNotNull { reaction ->
|
||||
if (reaction.name == name) {
|
||||
if (reaction.count > 1) {
|
||||
reaction.copy(
|
||||
count = reaction.count - 1,
|
||||
me = false
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
reaction
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
announcement
|
||||
Success(
|
||||
announcements.value!!.data!!.map { announcement ->
|
||||
if (announcement.id == announcementId) {
|
||||
announcement.copy(
|
||||
reactions = announcement.reactions.mapNotNull { reaction ->
|
||||
if (reaction.name == name) {
|
||||
if (reaction.count > 1) {
|
||||
reaction.copy(
|
||||
count = reaction.count - 1,
|
||||
me = false
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
reaction
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
} else {
|
||||
announcement
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}, {
|
||||
},
|
||||
{
|
||||
Log.w(TAG, "Failed to remove reaction from the announcement.", it)
|
||||
})
|
||||
.autoDispose()
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -32,7 +32,10 @@ import android.view.KeyEvent
|
|||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.ColorInt
|
||||
|
@ -70,7 +73,20 @@ import com.keylesspalace.tusky.entity.Emoji
|
|||
import com.keylesspalace.tusky.entity.NewPoll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.ComposeTokenizer
|
||||
import com.keylesspalace.tusky.util.PickMediaFiles
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.afterTextChanged
|
||||
import com.keylesspalace.tusky.util.combineLiveData
|
||||
import com.keylesspalace.tusky.util.combineOptionalLiveData
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.highlightSpans
|
||||
import com.keylesspalace.tusky.util.loadAvatar
|
||||
import com.keylesspalace.tusky.util.onTextChanged
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
import com.keylesspalace.tusky.util.withLifecycleContext
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||
import com.mikepenz.iconics.utils.colorInt
|
||||
|
@ -83,7 +99,8 @@ import javax.inject.Inject
|
|||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class ComposeActivity : BaseActivity(),
|
||||
class ComposeActivity :
|
||||
BaseActivity(),
|
||||
ComposeOptionsListener,
|
||||
ComposeAutoCompleteAdapter.AutocompletionProvider,
|
||||
OnEmojiSelectedListener,
|
||||
|
@ -288,8 +305,9 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
|
||||
// work around Android platform bug -> https://issuetracker.google.com/issues/67102093
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O
|
||||
|| Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1) {
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O ||
|
||||
Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1
|
||||
) {
|
||||
binding.composeEditField.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
|
||||
}
|
||||
}
|
||||
|
@ -330,9 +348,9 @@ class ComposeActivity : BaseActivity(),
|
|||
updateScheduleButton()
|
||||
}
|
||||
combineOptionalLiveData(viewModel.media, viewModel.poll) { media, poll ->
|
||||
val active = poll == null
|
||||
&& media!!.size != 4
|
||||
&& (media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE)
|
||||
val active = poll == null &&
|
||||
media!!.size != 4 &&
|
||||
(media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE)
|
||||
enableButton(binding.composeAddMediaButton, active, active)
|
||||
enablePollButton(media.isNullOrEmpty())
|
||||
}.subscribe()
|
||||
|
@ -393,7 +411,6 @@ class ComposeActivity : BaseActivity(),
|
|||
setDisplayShowHomeEnabled(true)
|
||||
setHomeAsUpIndicator(R.drawable.ic_close_24dp)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun setupAvatar(preferences: SharedPreferences, activeAccount: AccountEntity) {
|
||||
|
@ -409,8 +426,10 @@ class ComposeActivity : BaseActivity(),
|
|||
avatarSize / 8,
|
||||
animateAvatars
|
||||
)
|
||||
binding.composeAvatar.contentDescription = getString(R.string.compose_active_account_description,
|
||||
activeAccount.fullName)
|
||||
binding.composeAvatar.contentDescription = getString(
|
||||
R.string.compose_active_account_description,
|
||||
activeAccount.fullName
|
||||
)
|
||||
}
|
||||
|
||||
private fun replaceTextAtCaret(text: CharSequence) {
|
||||
|
@ -468,7 +487,6 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private fun atButtonClicked() {
|
||||
prependSelectedWordsWith("@")
|
||||
}
|
||||
|
@ -484,7 +502,7 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
private fun displayTransientError(@StringRes stringId: Int) {
|
||||
val bar = Snackbar.make(binding.activityCompose, stringId, Snackbar.LENGTH_LONG)
|
||||
//necessary so snackbar is shown over everything
|
||||
// necessary so snackbar is shown over everything
|
||||
bar.view.elevation = resources.getDimension(R.dimen.compose_activity_snackbar_elevation)
|
||||
bar.show()
|
||||
}
|
||||
|
@ -502,7 +520,6 @@ class ComposeActivity : BaseActivity(),
|
|||
binding.composeHideMediaButton.setImageResource(R.drawable.ic_hide_media_24dp)
|
||||
binding.composeHideMediaButton.isClickable = false
|
||||
ContextCompat.getColor(this, R.color.transparent_tusky_blue)
|
||||
|
||||
} else {
|
||||
binding.composeHideMediaButton.isClickable = true
|
||||
if (markMediaSensitive) {
|
||||
|
@ -611,13 +628,15 @@ class ComposeActivity : BaseActivity(),
|
|||
private fun onMediaPick() {
|
||||
addMediaBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
//Wait until bottom sheet is not collapsed and show next screen after
|
||||
// Wait until bottom sheet is not collapsed and show next screen after
|
||||
if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
addMediaBehavior.removeBottomSheetCallback(this)
|
||||
if (ContextCompat.checkSelfPermission(this@ComposeActivity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this@ComposeActivity,
|
||||
ActivityCompat.requestPermissions(
|
||||
this@ComposeActivity,
|
||||
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
|
||||
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE)
|
||||
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE
|
||||
)
|
||||
} else {
|
||||
pickMediaFile.launch(true)
|
||||
}
|
||||
|
@ -633,8 +652,10 @@ class ComposeActivity : BaseActivity(),
|
|||
private fun openPollDialog() {
|
||||
addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
val instanceParams = viewModel.instanceParams.value!!
|
||||
showAddPollDialog(this, viewModel.poll.value, instanceParams.pollMaxOptions,
|
||||
instanceParams.pollMaxLength, viewModel::updatePoll)
|
||||
showAddPollDialog(
|
||||
this, viewModel.poll.value, instanceParams.pollMaxOptions,
|
||||
instanceParams.pollMaxLength, viewModel::updatePoll
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupPollView() {
|
||||
|
@ -755,14 +776,17 @@ class ComposeActivity : BaseActivity(),
|
|||
if (viewModel.media.value!!.isNotEmpty()) {
|
||||
finishingUploadDialog = ProgressDialog.show(
|
||||
this, getString(R.string.dialog_title_finishing_media_upload),
|
||||
getString(R.string.dialog_message_uploading_media), true, true)
|
||||
getString(R.string.dialog_message_uploading_media), true, true
|
||||
)
|
||||
}
|
||||
|
||||
viewModel.sendStatus(contentText, spoilerText).observe(this, {
|
||||
finishingUploadDialog?.dismiss()
|
||||
deleteDraftAndFinish()
|
||||
})
|
||||
|
||||
viewModel.sendStatus(contentText, spoilerText).observe(
|
||||
this,
|
||||
{
|
||||
finishingUploadDialog?.dismiss()
|
||||
deleteDraftAndFinish()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
binding.composeEditField.error = getString(R.string.error_compose_character_limit)
|
||||
enableButtons(true)
|
||||
|
@ -776,10 +800,12 @@ class ComposeActivity : BaseActivity(),
|
|||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
pickMediaFile.launch(true)
|
||||
} else {
|
||||
Snackbar.make(binding.activityCompose, R.string.error_media_upload_permission,
|
||||
Snackbar.LENGTH_SHORT).apply {
|
||||
Snackbar.make(
|
||||
binding.activityCompose, R.string.error_media_upload_permission,
|
||||
Snackbar.LENGTH_SHORT
|
||||
).apply {
|
||||
setAction(R.string.action_retry) { onMediaPick() }
|
||||
//necessary so snackbar is shown over everything
|
||||
// necessary so snackbar is shown over everything
|
||||
view.elevation = resources.getDimension(R.dimen.compose_activity_snackbar_elevation)
|
||||
show()
|
||||
}
|
||||
|
@ -798,24 +824,30 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
|
||||
// Continue only if the File was successfully created
|
||||
photoUploadUri = FileProvider.getUriForFile(this,
|
||||
photoUploadUri = FileProvider.getUriForFile(
|
||||
this,
|
||||
BuildConfig.APPLICATION_ID + ".fileprovider",
|
||||
photoFile)
|
||||
photoFile
|
||||
)
|
||||
takePicture.launch(photoUploadUri)
|
||||
}
|
||||
|
||||
private fun enableButton(button: ImageButton, clickable: Boolean, colorActive: Boolean) {
|
||||
button.isEnabled = clickable
|
||||
ThemeUtils.setDrawableTint(this, button.drawable,
|
||||
ThemeUtils.setDrawableTint(
|
||||
this, button.drawable,
|
||||
if (colorActive) android.R.attr.textColorTertiary
|
||||
else R.attr.textColorDisabled)
|
||||
else R.attr.textColorDisabled
|
||||
)
|
||||
}
|
||||
|
||||
private fun enablePollButton(enable: Boolean) {
|
||||
binding.addPollTextActionTextView.isEnabled = enable
|
||||
val textColor = ThemeUtils.getColor(this,
|
||||
val textColor = ThemeUtils.getColor(
|
||||
this,
|
||||
if (enable) android.R.attr.textColorTertiary
|
||||
else R.attr.textColorDisabled)
|
||||
else R.attr.textColorDisabled
|
||||
)
|
||||
binding.addPollTextActionTextView.setTextColor(textColor)
|
||||
binding.addPollTextActionTextView.compoundDrawablesRelative[0].colorFilter = PorterDuffColorFilter(textColor, PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
|
@ -847,7 +879,6 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
displayTransientError(errorId)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -881,7 +912,8 @@ class ComposeActivity : BaseActivity(),
|
|||
if (composeOptionsBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
||||
addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
||||
emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
||||
scheduleBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
scheduleBehavior.state == BottomSheetBehavior.STATE_EXPANDED
|
||||
) {
|
||||
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
|
|
|
@ -30,9 +30,9 @@ import com.keylesspalace.tusky.R
|
|||
import com.keylesspalace.tusky.components.compose.view.ProgressImageView
|
||||
|
||||
class MediaPreviewAdapter(
|
||||
context: Context,
|
||||
private val onAddCaption: (ComposeActivity.QueuedMedia) -> Unit,
|
||||
private val onRemove: (ComposeActivity.QueuedMedia) -> Unit
|
||||
context: Context,
|
||||
private val onAddCaption: (ComposeActivity.QueuedMedia) -> Unit,
|
||||
private val onRemove: (ComposeActivity.QueuedMedia) -> Unit
|
||||
) : RecyclerView.Adapter<MediaPreviewAdapter.PreviewViewHolder>() {
|
||||
|
||||
fun submitList(list: List<ComposeActivity.QueuedMedia>) {
|
||||
|
@ -57,7 +57,7 @@ class MediaPreviewAdapter(
|
|||
}
|
||||
|
||||
private val thumbnailViewSize =
|
||||
context.resources.getDimensionPixelSize(R.dimen.compose_media_preview_size)
|
||||
context.resources.getDimensionPixelSize(R.dimen.compose_media_preview_size)
|
||||
|
||||
override fun getItemCount(): Int = differ.currentList.size
|
||||
|
||||
|
@ -74,31 +74,34 @@ class MediaPreviewAdapter(
|
|||
holder.progressImageView.setImageResource(R.drawable.ic_music_box_preview_24dp)
|
||||
} else {
|
||||
Glide.with(holder.itemView.context)
|
||||
.load(item.uri)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.dontAnimate()
|
||||
.into(holder.progressImageView)
|
||||
.load(item.uri)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.dontAnimate()
|
||||
.into(holder.progressImageView)
|
||||
}
|
||||
}
|
||||
|
||||
private val differ = AsyncListDiffer(this, object : DiffUtil.ItemCallback<ComposeActivity.QueuedMedia>() {
|
||||
override fun areItemsTheSame(oldItem: ComposeActivity.QueuedMedia, newItem: ComposeActivity.QueuedMedia): Boolean {
|
||||
return oldItem.localId == newItem.localId
|
||||
}
|
||||
private val differ = AsyncListDiffer(
|
||||
this,
|
||||
object : DiffUtil.ItemCallback<ComposeActivity.QueuedMedia>() {
|
||||
override fun areItemsTheSame(oldItem: ComposeActivity.QueuedMedia, newItem: ComposeActivity.QueuedMedia): Boolean {
|
||||
return oldItem.localId == newItem.localId
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: ComposeActivity.QueuedMedia, newItem: ComposeActivity.QueuedMedia): Boolean {
|
||||
return oldItem == newItem
|
||||
override fun areContentsTheSame(oldItem: ComposeActivity.QueuedMedia, newItem: ComposeActivity.QueuedMedia): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
inner class PreviewViewHolder(val progressImageView: ProgressImageView)
|
||||
: RecyclerView.ViewHolder(progressImageView) {
|
||||
inner class PreviewViewHolder(val progressImageView: ProgressImageView) :
|
||||
RecyclerView.ViewHolder(progressImageView) {
|
||||
init {
|
||||
val layoutParams = ConstraintLayout.LayoutParams(thumbnailViewSize, thumbnailViewSize)
|
||||
val margin = itemView.context.resources
|
||||
.getDimensionPixelSize(R.dimen.compose_media_preview_margin)
|
||||
.getDimensionPixelSize(R.dimen.compose_media_preview_margin)
|
||||
val marginBottom = itemView.context.resources
|
||||
.getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom)
|
||||
.getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom)
|
||||
layoutParams.setMargins(margin, 0, margin, marginBottom)
|
||||
progressImageView.layoutParams = layoutParams
|
||||
progressImageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
|
@ -107,4 +110,4 @@ class MediaPreviewAdapter(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,10 @@ import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia
|
|||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.network.ProgressRequestBody
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.MEDIA_SIZE_UNKNOWN
|
||||
import com.keylesspalace.tusky.util.getImageSquarePixels
|
||||
import com.keylesspalace.tusky.util.getMediaSize
|
||||
import com.keylesspalace.tusky.util.randomAlphanumericString
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
|
@ -37,7 +40,7 @@ import okhttp3.MultipartBody
|
|||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
|
||||
sealed class UploadEvent {
|
||||
data class ProgressEvent(val percentage: Int) : UploadEvent()
|
||||
|
@ -50,9 +53,9 @@ fun createNewImageFile(context: Context): File {
|
|||
val imageFileName = "Tusky_${randomId}_"
|
||||
val storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
|
||||
return File.createTempFile(
|
||||
imageFileName, /* prefix */
|
||||
".jpg", /* suffix */
|
||||
storageDir /* directory */
|
||||
imageFileName, /* prefix */
|
||||
".jpg", /* suffix */
|
||||
storageDir /* directory */
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -69,18 +72,18 @@ class MediaTypeException : Exception()
|
|||
class CouldNotOpenFileException : Exception()
|
||||
|
||||
class MediaUploaderImpl(
|
||||
private val context: Context,
|
||||
private val mastodonApi: MastodonApi
|
||||
private val context: Context,
|
||||
private val mastodonApi: MastodonApi
|
||||
) : MediaUploader {
|
||||
override fun uploadMedia(media: QueuedMedia): Observable<UploadEvent> {
|
||||
return Observable
|
||||
.fromCallable {
|
||||
if (shouldResizeMedia(media)) {
|
||||
downsize(media)
|
||||
} else media
|
||||
}
|
||||
.switchMap { upload(it) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.fromCallable {
|
||||
if (shouldResizeMedia(media)) {
|
||||
downsize(media)
|
||||
} else media
|
||||
}
|
||||
.switchMap { upload(it) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
override fun prepareMedia(inUri: Uri): Single<PreparedMedia> {
|
||||
|
@ -101,12 +104,13 @@ class MediaUploaderImpl(
|
|||
val file = File.createTempFile("randomTemp1", suffix, context.cacheDir)
|
||||
FileOutputStream(file.absoluteFile).use { out ->
|
||||
input.copyTo(out)
|
||||
uri = FileProvider.getUriForFile(context,
|
||||
BuildConfig.APPLICATION_ID + ".fileprovider",
|
||||
file)
|
||||
uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
BuildConfig.APPLICATION_ID + ".fileprovider",
|
||||
file
|
||||
)
|
||||
mediaSize = getMediaSize(contentResolver, uri)
|
||||
}
|
||||
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, e)
|
||||
|
@ -151,20 +155,22 @@ class MediaUploaderImpl(
|
|||
var mimeType = contentResolver.getType(media.uri)
|
||||
val map = MimeTypeMap.getSingleton()
|
||||
val fileExtension = map.getExtensionFromMimeType(mimeType)
|
||||
val filename = String.format("%s_%s_%s.%s",
|
||||
context.getString(R.string.app_name),
|
||||
Date().time.toString(),
|
||||
randomAlphanumericString(10),
|
||||
fileExtension)
|
||||
val filename = "%s_%s_%s.%s".format(
|
||||
context.getString(R.string.app_name),
|
||||
Date().time.toString(),
|
||||
randomAlphanumericString(10),
|
||||
fileExtension
|
||||
)
|
||||
|
||||
val stream = contentResolver.openInputStream(media.uri)
|
||||
|
||||
if (mimeType == null) mimeType = "multipart/form-data"
|
||||
|
||||
|
||||
var lastProgress = -1
|
||||
val fileBody = ProgressRequestBody(stream, media.mediaSize,
|
||||
mimeType.toMediaTypeOrNull()) { percentage ->
|
||||
val fileBody = ProgressRequestBody(
|
||||
stream, media.mediaSize,
|
||||
mimeType.toMediaTypeOrNull()
|
||||
) { percentage ->
|
||||
if (percentage != lastProgress) {
|
||||
emitter.onNext(UploadEvent.ProgressEvent(percentage))
|
||||
}
|
||||
|
@ -180,12 +186,15 @@ class MediaUploaderImpl(
|
|||
}
|
||||
|
||||
val uploadDisposable = mastodonApi.uploadMedia(body, description)
|
||||
.subscribe({ attachment ->
|
||||
.subscribe(
|
||||
{ attachment ->
|
||||
emitter.onNext(UploadEvent.FinishedEvent(attachment))
|
||||
emitter.onComplete()
|
||||
}, { e ->
|
||||
},
|
||||
{ e ->
|
||||
emitter.onError(e)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// Cancel the request when our observable is cancelled
|
||||
emitter.setDisposable(uploadDisposable)
|
||||
|
@ -194,15 +203,16 @@ class MediaUploaderImpl(
|
|||
|
||||
private fun downsize(media: QueuedMedia): QueuedMedia {
|
||||
val file = createNewImageFile(context)
|
||||
DownsizeImageTask.resize(arrayOf(media.uri),
|
||||
STATUS_IMAGE_SIZE_LIMIT, context.contentResolver, file)
|
||||
DownsizeImageTask.resize(
|
||||
arrayOf(media.uri),
|
||||
STATUS_IMAGE_SIZE_LIMIT, context.contentResolver, file
|
||||
)
|
||||
return media.copy(uri = file.toUri(), mediaSize = file.length())
|
||||
}
|
||||
|
||||
private fun shouldResizeMedia(media: QueuedMedia): Boolean {
|
||||
return media.type == QueuedMedia.Type.IMAGE
|
||||
&& (media.mediaSize > STATUS_IMAGE_SIZE_LIMIT
|
||||
|| getImageSquarePixels(context.contentResolver, media.uri) > STATUS_IMAGE_PIXEL_SIZE_LIMIT)
|
||||
return media.type == QueuedMedia.Type.IMAGE &&
|
||||
(media.mediaSize > STATUS_IMAGE_SIZE_LIMIT || getImageSquarePixels(context.contentResolver, media.uri) > STATUS_IMAGE_PIXEL_SIZE_LIMIT)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
@ -211,6 +221,5 @@ class MediaUploaderImpl(
|
|||
private const val STATUS_AUDIO_SIZE_LIMIT = 41943040 // 40MiB
|
||||
private const val STATUS_IMAGE_SIZE_LIMIT = 8388608 // 8MiB
|
||||
private const val STATUS_IMAGE_PIXEL_SIZE_LIMIT = 16777216 // 4096^2 Pixels
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,33 +26,33 @@ import com.keylesspalace.tusky.databinding.DialogAddPollBinding
|
|||
import com.keylesspalace.tusky.entity.NewPoll
|
||||
|
||||
fun showAddPollDialog(
|
||||
context: Context,
|
||||
poll: NewPoll?,
|
||||
maxOptionCount: Int,
|
||||
maxOptionLength: Int,
|
||||
onUpdatePoll: (NewPoll) -> Unit
|
||||
context: Context,
|
||||
poll: NewPoll?,
|
||||
maxOptionCount: Int,
|
||||
maxOptionLength: Int,
|
||||
onUpdatePoll: (NewPoll) -> Unit
|
||||
) {
|
||||
|
||||
val binding = DialogAddPollBinding.inflate(LayoutInflater.from(context))
|
||||
|
||||
val dialog = AlertDialog.Builder(context)
|
||||
.setIcon(R.drawable.ic_poll_24dp)
|
||||
.setTitle(R.string.create_poll_title)
|
||||
.setView(binding.root)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create()
|
||||
.setIcon(R.drawable.ic_poll_24dp)
|
||||
.setTitle(R.string.create_poll_title)
|
||||
.setView(binding.root)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create()
|
||||
|
||||
val adapter = AddPollOptionsAdapter(
|
||||
options = poll?.options?.toMutableList() ?: mutableListOf("", ""),
|
||||
maxOptionLength = maxOptionLength,
|
||||
onOptionRemoved = { valid ->
|
||||
binding.addChoiceButton.isEnabled = true
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = valid
|
||||
},
|
||||
onOptionChanged = { valid ->
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = valid
|
||||
}
|
||||
options = poll?.options?.toMutableList() ?: mutableListOf("", ""),
|
||||
maxOptionLength = maxOptionLength,
|
||||
onOptionRemoved = { valid ->
|
||||
binding.addChoiceButton.isEnabled = true
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = valid
|
||||
},
|
||||
onOptionChanged = { valid ->
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = valid
|
||||
}
|
||||
)
|
||||
|
||||
binding.pollChoices.adapter = adapter
|
||||
|
@ -80,13 +80,15 @@ fun showAddPollDialog(
|
|||
val selectedPollDurationId = binding.pollDurationSpinner.selectedItemPosition
|
||||
|
||||
val pollDuration = context.resources
|
||||
.getIntArray(R.array.poll_duration_values)[selectedPollDurationId]
|
||||
.getIntArray(R.array.poll_duration_values)[selectedPollDurationId]
|
||||
|
||||
onUpdatePoll(NewPoll(
|
||||
onUpdatePoll(
|
||||
NewPoll(
|
||||
options = adapter.pollOptions,
|
||||
expiresIn = pollDuration,
|
||||
multiple = binding.multipleChoicesCheckBox.isChecked
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
@ -96,4 +98,4 @@ fun showAddPollDialog(
|
|||
|
||||
// make the dialog focusable so the keyboard does not stay behind it
|
||||
dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,11 +27,11 @@ import com.keylesspalace.tusky.util.onTextChanged
|
|||
import com.keylesspalace.tusky.util.visible
|
||||
|
||||
class AddPollOptionsAdapter(
|
||||
private var options: MutableList<String>,
|
||||
private val maxOptionLength: Int,
|
||||
private val onOptionRemoved: (Boolean) -> Unit,
|
||||
private val onOptionChanged: (Boolean) -> Unit
|
||||
): RecyclerView.Adapter<BindingHolder<ItemAddPollOptionBinding>>() {
|
||||
private var options: MutableList<String>,
|
||||
private val maxOptionLength: Int,
|
||||
private val onOptionRemoved: (Boolean) -> Unit,
|
||||
private val onOptionChanged: (Boolean) -> Unit
|
||||
) : RecyclerView.Adapter<BindingHolder<ItemAddPollOptionBinding>>() {
|
||||
|
||||
val pollOptions: List<String>
|
||||
get() = options.toList()
|
||||
|
@ -48,7 +48,7 @@ class AddPollOptionsAdapter(
|
|||
|
||||
binding.optionEditText.onTextChanged { s, _, _, _ ->
|
||||
val pos = holder.bindingAdapterPosition
|
||||
if(pos != RecyclerView.NO_POSITION) {
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
options[pos] = s.toString()
|
||||
onOptionChanged(validateInput())
|
||||
}
|
||||
|
|
|
@ -40,9 +40,10 @@ import com.keylesspalace.tusky.util.withLifecycleContext
|
|||
// https://github.com/tootsuite/mastodon/blob/c6904c0d3766a2ea8a81ab025c127169ecb51373/app/models/media_attachment.rb#L32
|
||||
private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 1500
|
||||
|
||||
fun <T> T.makeCaptionDialog(existingDescription: String?,
|
||||
previewUri: Uri,
|
||||
onUpdateDescription: (String) -> LiveData<Boolean>
|
||||
fun <T> T.makeCaptionDialog(
|
||||
existingDescription: String?,
|
||||
previewUri: Uri,
|
||||
onUpdateDescription: (String) -> LiveData<Boolean>
|
||||
) where T : Activity, T : LifecycleOwner {
|
||||
val dialogLayout = LinearLayout(this)
|
||||
val padding = Utils.dpToPx(this, 8)
|
||||
|
@ -60,14 +61,18 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
|
|||
(imageView.layoutParams as LinearLayout.LayoutParams).setMargins(0, margin, 0, 0)
|
||||
|
||||
val input = EditText(this)
|
||||
input.hint = resources.getQuantityString(R.plurals.hint_describe_for_visually_impaired,
|
||||
MEDIA_DESCRIPTION_CHARACTER_LIMIT, MEDIA_DESCRIPTION_CHARACTER_LIMIT)
|
||||
input.hint = resources.getQuantityString(
|
||||
R.plurals.hint_describe_for_visually_impaired,
|
||||
MEDIA_DESCRIPTION_CHARACTER_LIMIT, MEDIA_DESCRIPTION_CHARACTER_LIMIT
|
||||
)
|
||||
dialogLayout.addView(input)
|
||||
(input.layoutParams as LinearLayout.LayoutParams).setMargins(margin, margin, margin, margin)
|
||||
input.setLines(2)
|
||||
input.inputType = (InputType.TYPE_CLASS_TEXT
|
||||
or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES)
|
||||
input.inputType = (
|
||||
InputType.TYPE_CLASS_TEXT
|
||||
or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||
)
|
||||
input.setText(existingDescription)
|
||||
input.filters = arrayOf(InputFilter.LengthFilter(MEDIA_DESCRIPTION_CHARACTER_LIMIT))
|
||||
|
||||
|
@ -75,41 +80,40 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
|
|||
onUpdateDescription(input.text.toString())
|
||||
withLifecycleContext {
|
||||
onUpdateDescription(input.text.toString())
|
||||
.observe { success -> if (!success) showFailedCaptionMessage() }
|
||||
|
||||
.observe { success -> if (!success) showFailedCaptionMessage() }
|
||||
}
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setView(dialogLayout)
|
||||
.setPositiveButton(android.R.string.ok, okListener)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
.setView(dialogLayout)
|
||||
.setPositiveButton(android.R.string.ok, okListener)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
|
||||
val window = dialog.window
|
||||
window?.setSoftInputMode(
|
||||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||
)
|
||||
|
||||
dialog.show()
|
||||
|
||||
// Load the image and manually set it into the ImageView because it doesn't have a fixed size.
|
||||
Glide.with(this)
|
||||
.load(previewUri)
|
||||
.downsample(DownsampleStrategy.CENTER_INSIDE)
|
||||
.into(object : CustomTarget<Drawable>(4096, 4096) {
|
||||
override fun onLoadCleared(placeholder: Drawable?) {
|
||||
imageView.setImageDrawable(placeholder)
|
||||
}
|
||||
.load(previewUri)
|
||||
.downsample(DownsampleStrategy.CENTER_INSIDE)
|
||||
.into(object : CustomTarget<Drawable>(4096, 4096) {
|
||||
override fun onLoadCleared(placeholder: Drawable?) {
|
||||
imageView.setImageDrawable(placeholder)
|
||||
}
|
||||
|
||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||
imageView.setImageDrawable(resource)
|
||||
}
|
||||
})
|
||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||
imageView.setImageDrawable(resource)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
private fun Activity.showFailedCaptionMessage() {
|
||||
Toast.makeText(this, R.string.error_failed_set_caption, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
|
|
@ -57,12 +57,10 @@ class ComposeOptionsView @JvmOverloads constructor(context: Context, attrs: Attr
|
|||
R.id.directRadioButton
|
||||
else ->
|
||||
R.id.directRadioButton
|
||||
|
||||
}
|
||||
|
||||
check(selectedButton)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface ComposeOptionsListener {
|
||||
|
|
|
@ -16,25 +16,27 @@
|
|||
package com.keylesspalace.tusky.components.compose.view
|
||||
|
||||
import android.content.Context
|
||||
import androidx.emoji.widget.EmojiEditTextHelper
|
||||
import androidx.core.view.inputmethod.EditorInfoCompat
|
||||
import androidx.core.view.inputmethod.InputConnectionCompat
|
||||
import androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView
|
||||
import android.text.InputType
|
||||
import android.text.method.KeyListener
|
||||
import android.util.AttributeSet
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputConnection
|
||||
import androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView
|
||||
import androidx.core.view.inputmethod.EditorInfoCompat
|
||||
import androidx.core.view.inputmethod.InputConnectionCompat
|
||||
import androidx.emoji.widget.EmojiEditTextHelper
|
||||
|
||||
class EditTextTyped @JvmOverloads constructor(context: Context,
|
||||
attributeSet: AttributeSet? = null)
|
||||
: AppCompatMultiAutoCompleteTextView(context, attributeSet) {
|
||||
class EditTextTyped @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attributeSet: AttributeSet? = null
|
||||
) :
|
||||
AppCompatMultiAutoCompleteTextView(context, attributeSet) {
|
||||
|
||||
private var onCommitContentListener: InputConnectionCompat.OnCommitContentListener? = null
|
||||
private val emojiEditTextHelper: EmojiEditTextHelper = EmojiEditTextHelper(this)
|
||||
|
||||
init {
|
||||
//fix a bug with autocomplete and some keyboards
|
||||
// fix a bug with autocomplete and some keyboards
|
||||
val newInputType = inputType and (inputType xor InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE)
|
||||
inputType = newInputType
|
||||
super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener))
|
||||
|
@ -52,8 +54,13 @@ class EditTextTyped @JvmOverloads constructor(context: Context,
|
|||
val connection = super.onCreateInputConnection(editorInfo)
|
||||
return if (onCommitContentListener != null) {
|
||||
EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*"))
|
||||
getEmojiEditTextHelper().onCreateInputConnection(InputConnectionCompat.createWrapper(connection, editorInfo,
|
||||
onCommitContentListener!!), editorInfo)!!
|
||||
getEmojiEditTextHelper().onCreateInputConnection(
|
||||
InputConnectionCompat.createWrapper(
|
||||
connection, editorInfo,
|
||||
onCommitContentListener!!
|
||||
),
|
||||
editorInfo
|
||||
)!!
|
||||
} else {
|
||||
connection
|
||||
}
|
||||
|
|
|
@ -25,10 +25,11 @@ import com.keylesspalace.tusky.databinding.ViewPollPreviewBinding
|
|||
import com.keylesspalace.tusky.entity.NewPoll
|
||||
|
||||
class PollPreviewView @JvmOverloads constructor(
|
||||
context: Context?,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0)
|
||||
: LinearLayout(context, attrs, defStyleAttr) {
|
||||
context: Context?,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) :
|
||||
LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val adapter = PreviewPollOptionsAdapter()
|
||||
|
||||
|
@ -46,7 +47,7 @@ class PollPreviewView @JvmOverloads constructor(
|
|||
binding.pollPreviewOptions.adapter = adapter
|
||||
}
|
||||
|
||||
fun setPoll(poll: NewPoll){
|
||||
fun setPoll(poll: NewPoll) {
|
||||
adapter.update(poll.options, poll.multiple)
|
||||
|
||||
val pollDurationId = resources.getIntArray(R.array.poll_duration_values).indexOfLast {
|
||||
|
@ -59,4 +60,4 @@ class PollPreviewView @JvmOverloads constructor(
|
|||
super.setOnClickListener(l)
|
||||
adapter.setOnClickListener(l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,15 +28,15 @@ import com.mikepenz.iconics.utils.sizeDp
|
|||
|
||||
class TootButton
|
||||
@JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : MaterialButton(context, attrs, defStyleAttr) {
|
||||
|
||||
private val smallStyle: Boolean = context.resources.getBoolean(R.bool.show_small_toot_button)
|
||||
|
||||
init {
|
||||
if(smallStyle) {
|
||||
if (smallStyle) {
|
||||
setIconResource(R.drawable.ic_send_24dp)
|
||||
} else {
|
||||
setText(R.string.action_send)
|
||||
|
@ -47,7 +47,7 @@ class TootButton
|
|||
}
|
||||
|
||||
fun setStatusVisibility(visibility: Status.Visibility) {
|
||||
if(!smallStyle) {
|
||||
if (!smallStyle) {
|
||||
|
||||
icon = when (visibility) {
|
||||
Status.Visibility.PUBLIC -> {
|
||||
|
@ -68,8 +68,5 @@ class TootButton
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ import com.keylesspalace.tusky.entity.Status
|
|||
import com.keylesspalace.tusky.util.shouldTrimStatus
|
||||
import java.util.Date
|
||||
|
||||
@Entity(primaryKeys = ["id","accountId"])
|
||||
@Entity(primaryKeys = ["id", "accountId"])
|
||||
@TypeConverters(Converters::class)
|
||||
data class ConversationEntity(
|
||||
val accountId: Long,
|
||||
|
@ -98,7 +98,7 @@ data class ConversationStatusEntity(
|
|||
if (inReplyToId != other.inReplyToId) return false
|
||||
if (inReplyToAccountId != other.inReplyToAccountId) return false
|
||||
if (account != other.account) return false
|
||||
if (content.toString() != other.content.toString()) return false //TODO find a better method to compare two spanned strings
|
||||
if (content.toString() != other.content.toString()) return false // TODO find a better method to compare two spanned strings
|
||||
if (createdAt != other.createdAt) return false
|
||||
if (emojis != other.emojis) return false
|
||||
if (favouritesCount != other.favouritesCount) return false
|
||||
|
@ -157,7 +157,7 @@ data class ConversationStatusEntity(
|
|||
reblogged = false,
|
||||
favourited = favourited,
|
||||
bookmarked = bookmarked,
|
||||
sensitive= sensitive,
|
||||
sensitive = sensitive,
|
||||
spoilerText = spoilerText,
|
||||
visibility = Status.Visibility.DIRECT,
|
||||
attachments = attachments,
|
||||
|
@ -166,7 +166,8 @@ data class ConversationStatusEntity(
|
|||
pinned = false,
|
||||
muted = muted,
|
||||
poll = poll,
|
||||
card = null)
|
||||
card = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,5 +37,4 @@ class ConversationLoadStateAdapter(
|
|||
val binding = ItemNetworkStateBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return NetworkStateViewHolder(binding, retryCallback)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,15 +40,16 @@ import com.keylesspalace.tusky.fragment.SFragment
|
|||
import com.keylesspalace.tusky.interfaces.ReselectableFragment
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
import com.keylesspalace.tusky.util.CardViewMode
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
class ConversationsFragment : SFragment(), StatusActionListener, Injectable, ReselectableFragment {
|
||||
|
|
|
@ -32,7 +32,6 @@ class ConversationsRepository @Inject constructor(
|
|||
Single.fromCallable {
|
||||
db.conversationDao().deleteForAccount(accountId)
|
||||
}.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,18 +28,17 @@ import com.keylesspalace.tusky.R
|
|||
import com.keylesspalace.tusky.db.DraftAttachment
|
||||
|
||||
class DraftMediaAdapter(
|
||||
private val attachmentClick: () -> Unit
|
||||
private val attachmentClick: () -> Unit
|
||||
) : ListAdapter<DraftAttachment, DraftMediaAdapter.DraftMediaViewHolder>(
|
||||
object: DiffUtil.ItemCallback<DraftAttachment>() {
|
||||
override fun areItemsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
object : DiffUtil.ItemCallback<DraftAttachment>() {
|
||||
override fun areItemsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DraftMediaViewHolder {
|
||||
|
@ -52,24 +51,24 @@ class DraftMediaAdapter(
|
|||
holder.imageView.setImageResource(R.drawable.ic_music_box_preview_24dp)
|
||||
} else {
|
||||
Glide.with(holder.itemView.context)
|
||||
.load(attachment.uri)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.dontAnimate()
|
||||
.into(holder.imageView)
|
||||
.load(attachment.uri)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.dontAnimate()
|
||||
.into(holder.imageView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class DraftMediaViewHolder(val imageView: ImageView)
|
||||
: RecyclerView.ViewHolder(imageView) {
|
||||
inner class DraftMediaViewHolder(val imageView: ImageView) :
|
||||
RecyclerView.ViewHolder(imageView) {
|
||||
init {
|
||||
val thumbnailViewSize =
|
||||
imageView.context.resources.getDimensionPixelSize(R.dimen.compose_media_preview_size)
|
||||
imageView.context.resources.getDimensionPixelSize(R.dimen.compose_media_preview_size)
|
||||
val layoutParams = ConstraintLayout.LayoutParams(thumbnailViewSize, thumbnailViewSize)
|
||||
val margin = itemView.context.resources
|
||||
.getDimensionPixelSize(R.dimen.compose_media_preview_margin)
|
||||
.getDimensionPixelSize(R.dimen.compose_media_preview_margin)
|
||||
val marginBottom = itemView.context.resources
|
||||
.getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom)
|
||||
.getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom)
|
||||
layoutParams.setMargins(margin, 0, margin, marginBottom)
|
||||
imageView.layoutParams = layoutParams
|
||||
imageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
|
@ -78,4 +77,4 @@ class DraftMediaAdapter(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,27 +91,28 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
|
|||
if (draft.inReplyToId != null) {
|
||||
bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
viewModel.getToot(draft.inReplyToId)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this))
|
||||
.subscribe({ status ->
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this))
|
||||
.subscribe(
|
||||
{ status ->
|
||||
val composeOptions = ComposeActivity.ComposeOptions(
|
||||
draftId = draft.id,
|
||||
tootText = draft.content,
|
||||
contentWarning = draft.contentWarning,
|
||||
inReplyToId = draft.inReplyToId,
|
||||
replyingStatusContent = status.content.toString(),
|
||||
replyingStatusAuthor = status.account.localUsername,
|
||||
draftAttachments = draft.attachments,
|
||||
poll = draft.poll,
|
||||
sensitive = draft.sensitive,
|
||||
visibility = draft.visibility
|
||||
draftId = draft.id,
|
||||
tootText = draft.content,
|
||||
contentWarning = draft.contentWarning,
|
||||
inReplyToId = draft.inReplyToId,
|
||||
replyingStatusContent = status.content.toString(),
|
||||
replyingStatusAuthor = status.account.localUsername,
|
||||
draftAttachments = draft.attachments,
|
||||
poll = draft.poll,
|
||||
sensitive = draft.sensitive,
|
||||
visibility = draft.visibility
|
||||
)
|
||||
|
||||
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
|
||||
startActivity(ComposeActivity.startIntent(this, composeOptions))
|
||||
|
||||
}, { throwable ->
|
||||
},
|
||||
{ throwable ->
|
||||
|
||||
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
|
||||
|
@ -124,9 +125,10 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
|
|||
openDraftWithoutReply(draft)
|
||||
} else {
|
||||
Snackbar.make(binding.root, getString(R.string.drafts_failed_loading_reply), Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
.show()
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
} else {
|
||||
openDraftWithoutReply(draft)
|
||||
}
|
||||
|
@ -134,13 +136,13 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
|
|||
|
||||
private fun openDraftWithoutReply(draft: DraftEntity) {
|
||||
val composeOptions = ComposeActivity.ComposeOptions(
|
||||
draftId = draft.id,
|
||||
tootText = draft.content,
|
||||
contentWarning = draft.contentWarning,
|
||||
draftAttachments = draft.attachments,
|
||||
poll = draft.poll,
|
||||
sensitive = draft.sensitive,
|
||||
visibility = draft.visibility
|
||||
draftId = draft.id,
|
||||
tootText = draft.content,
|
||||
contentWarning = draft.contentWarning,
|
||||
draftAttachments = draft.attachments,
|
||||
poll = draft.poll,
|
||||
sensitive = draft.sensitive,
|
||||
visibility = draft.visibility
|
||||
)
|
||||
|
||||
startActivity(ComposeActivity.startIntent(this, composeOptions))
|
||||
|
@ -149,10 +151,10 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
|
|||
override fun onDeleteDraft(draft: DraftEntity) {
|
||||
viewModel.deleteDraft(draft)
|
||||
Snackbar.make(binding.root, getString(R.string.draft_deleted), Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_undo) {
|
||||
viewModel.restoreDraft(draft)
|
||||
}
|
||||
.show()
|
||||
.setAction(R.string.action_undo) {
|
||||
viewModel.restoreDraft(draft)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -9,7 +9,7 @@ import dagger.android.DispatchingAndroidInjector
|
|||
import dagger.android.HasAndroidInjector
|
||||
import javax.inject.Inject
|
||||
|
||||
class InstanceListActivity: BaseActivity(), HasAndroidInjector {
|
||||
class InstanceListActivity : BaseActivity(), HasAndroidInjector {
|
||||
|
||||
@Inject
|
||||
lateinit var androidInjector: DispatchingAndroidInjector<Any>
|
||||
|
@ -27,11 +27,10 @@ class InstanceListActivity: BaseActivity(), HasAndroidInjector {
|
|||
}
|
||||
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.fragment_container, InstanceListFragment())
|
||||
.commit()
|
||||
.beginTransaction()
|
||||
.replace(R.id.fragment_container, InstanceListFragment())
|
||||
.commit()
|
||||
}
|
||||
|
||||
override fun androidInjector() = androidInjector
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ import com.keylesspalace.tusky.databinding.ItemMutedDomainBinding
|
|||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
|
||||
class DomainMutesAdapter(
|
||||
private val actionListener: InstanceActionListener
|
||||
): RecyclerView.Adapter<BindingHolder<ItemMutedDomainBinding>>() {
|
||||
private val actionListener: InstanceActionListener
|
||||
) : RecyclerView.Adapter<BindingHolder<ItemMutedDomainBinding>>() {
|
||||
|
||||
var instances: MutableList<String> = mutableListOf()
|
||||
var bottomLoading: Boolean = false
|
||||
|
|
|
@ -29,7 +29,7 @@ import retrofit2.Response
|
|||
import java.io.IOException
|
||||
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
|
||||
lateinit var api: MastodonApi
|
||||
|
@ -65,7 +65,7 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
|||
|
||||
override fun mute(mute: Boolean, instance: String, position: Int) {
|
||||
if (mute) {
|
||||
api.blockDomain(instance).enqueue(object: Callback<Any> {
|
||||
api.blockDomain(instance).enqueue(object : Callback<Any> {
|
||||
override fun onFailure(call: Call<Any>, t: Throwable) {
|
||||
Log.e(TAG, "Error muting domain $instance")
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
|||
}
|
||||
})
|
||||
} else {
|
||||
api.unblockDomain(instance).enqueue(object: Callback<Any> {
|
||||
api.unblockDomain(instance).enqueue(object : Callback<Any> {
|
||||
override fun onFailure(call: Call<Any>, t: Throwable) {
|
||||
Log.e(TAG, "Error unmuting domain $instance")
|
||||
}
|
||||
|
@ -88,10 +88,10 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
|||
if (response.isSuccessful) {
|
||||
adapter.removeItem(position)
|
||||
Snackbar.make(binding.recyclerView, getString(R.string.confirmation_domain_unmuted, instance), Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_undo) {
|
||||
mute(true, instance, position)
|
||||
}
|
||||
.show()
|
||||
.setAction(R.string.action_undo) {
|
||||
mute(true, instance, position)
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
Log.e(TAG, "Error unmuting domain $instance")
|
||||
}
|
||||
|
@ -112,9 +112,10 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
|||
}
|
||||
|
||||
api.domainBlocks(id, bottomId)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe({ response ->
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe(
|
||||
{ response ->
|
||||
val instances = response.body()
|
||||
|
||||
if (response.isSuccessful && instances != null) {
|
||||
|
@ -122,9 +123,11 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
|||
} else {
|
||||
onFetchInstancesFailure(Exception(response.message()))
|
||||
}
|
||||
}, {throwable ->
|
||||
},
|
||||
{ throwable ->
|
||||
onFetchInstancesFailure(throwable)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun onFetchInstancesSuccess(instances: List<String>, linkHeader: String?) {
|
||||
|
@ -141,9 +144,9 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
|||
if (adapter.itemCount == 0) {
|
||||
binding.messageView.show()
|
||||
binding.messageView.setup(
|
||||
R.drawable.elephant_friend_empty,
|
||||
R.string.message_empty,
|
||||
null
|
||||
R.drawable.elephant_friend_empty,
|
||||
R.string.message_empty,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
binding.messageView.hide()
|
||||
|
@ -174,4 +177,4 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
|||
companion object {
|
||||
private const val TAG = "InstanceList" // logging tag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,4 +2,4 @@ package com.keylesspalace.tusky.components.instancemute.interfaces
|
|||
|
||||
interface InstanceActionListener {
|
||||
fun mute(mute: Boolean, instance: String, position: Int)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ import com.keylesspalace.tusky.util.isLessThan
|
|||
import javax.inject.Inject
|
||||
|
||||
class NotificationFetcher @Inject constructor(
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val accountManager: AccountManager,
|
||||
private val notifier: Notifier
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val accountManager: AccountManager,
|
||||
private val notifier: Notifier
|
||||
) {
|
||||
fun fetchAndShow() {
|
||||
for (account in accountManager.getAllAccountsOrderedByActive()) {
|
||||
|
@ -39,9 +39,9 @@ class NotificationFetcher @Inject constructor(
|
|||
}
|
||||
Log.d(TAG, "getting Notifications for " + account.fullName)
|
||||
val notifications = mastodonApi.notificationsWithAuth(
|
||||
authHeader,
|
||||
account.domain,
|
||||
account.lastNotificationId
|
||||
authHeader,
|
||||
account.domain,
|
||||
account.lastNotificationId
|
||||
).blockingGet()
|
||||
|
||||
val newId = account.lastNotificationId
|
||||
|
@ -63,9 +63,9 @@ class NotificationFetcher @Inject constructor(
|
|||
private fun fetchMarker(authHeader: String, account: AccountEntity): Marker? {
|
||||
return try {
|
||||
val allMarkers = mastodonApi.markersWithAuth(
|
||||
authHeader,
|
||||
account.domain,
|
||||
listOf("notifications")
|
||||
authHeader,
|
||||
account.domain,
|
||||
listOf("notifications")
|
||||
).blockingGet()
|
||||
val notificationMarker = allMarkers["notifications"]
|
||||
Log.d(TAG, "Fetched marker: $notificationMarker")
|
||||
|
@ -79,4 +79,4 @@ class NotificationFetcher @Inject constructor(
|
|||
companion object {
|
||||
const val TAG = "NotificationFetcher"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ import androidx.work.WorkerParameters
|
|||
import javax.inject.Inject
|
||||
|
||||
class NotificationWorker(
|
||||
context: Context,
|
||||
params: WorkerParameters,
|
||||
private val notificationsFetcher: NotificationFetcher
|
||||
context: Context,
|
||||
params: WorkerParameters,
|
||||
private val notificationsFetcher: NotificationFetcher
|
||||
) : Worker(context, params) {
|
||||
|
||||
override fun doWork(): Result {
|
||||
|
@ -35,13 +35,13 @@ class NotificationWorker(
|
|||
}
|
||||
|
||||
class NotificationWorkerFactory @Inject constructor(
|
||||
private val notificationsFetcher: NotificationFetcher
|
||||
private val notificationsFetcher: NotificationFetcher
|
||||
) : WorkerFactory() {
|
||||
|
||||
override fun createWorker(
|
||||
appContext: Context,
|
||||
workerClassName: String,
|
||||
workerParameters: WorkerParameters
|
||||
appContext: Context,
|
||||
workerClassName: String,
|
||||
workerParameters: WorkerParameters
|
||||
): ListenableWorker? {
|
||||
if (workerClassName == NotificationWorker::class.java.name) {
|
||||
return NotificationWorker(appContext, workerParameters, notificationsFetcher)
|
||||
|
|
|
@ -12,9 +12,9 @@ interface Notifier {
|
|||
}
|
||||
|
||||
class SystemNotifier(
|
||||
private val context: Context
|
||||
private val context: Context
|
||||
) : Notifier {
|
||||
override fun show(notification: Notification, account: AccountEntity, isFirstInBatch: Boolean) {
|
||||
NotificationHelper.make(context, notification, account, isFirstInBatch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,11 @@ import android.util.Log
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.keylesspalace.tusky.*
|
||||
import com.keylesspalace.tusky.AccountListActivity
|
||||
import com.keylesspalace.tusky.BuildConfig
|
||||
import com.keylesspalace.tusky.FiltersActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.TabPreferenceActivity
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||
import com.keylesspalace.tusky.components.instancemute.InstanceListActivity
|
||||
|
@ -33,7 +37,12 @@ import com.keylesspalace.tusky.entity.Account
|
|||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.settings.*
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.settings.listPreference
|
||||
import com.keylesspalace.tusky.settings.makePreferenceScreen
|
||||
import com.keylesspalace.tusky.settings.preference
|
||||
import com.keylesspalace.tusky.settings.preferenceCategory
|
||||
import com.keylesspalace.tusky.settings.switchPreference
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||
|
@ -75,8 +84,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
setOnPreferenceClickListener {
|
||||
val intent = Intent(context, TabPreferenceActivity::class.java)
|
||||
activity?.startActivity(intent)
|
||||
activity?.overridePendingTransition(R.anim.slide_from_right,
|
||||
R.anim.slide_to_left)
|
||||
activity?.overridePendingTransition(
|
||||
R.anim.slide_from_right,
|
||||
R.anim.slide_to_left
|
||||
)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
@ -88,8 +99,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
val intent = Intent(context, AccountListActivity::class.java)
|
||||
intent.putExtra("type", AccountListActivity.Type.MUTES)
|
||||
activity?.startActivity(intent)
|
||||
activity?.overridePendingTransition(R.anim.slide_from_right,
|
||||
R.anim.slide_to_left)
|
||||
activity?.overridePendingTransition(
|
||||
R.anim.slide_from_right,
|
||||
R.anim.slide_to_left
|
||||
)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
@ -104,8 +117,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
val intent = Intent(context, AccountListActivity::class.java)
|
||||
intent.putExtra("type", AccountListActivity.Type.BLOCKS)
|
||||
activity?.startActivity(intent)
|
||||
activity?.overridePendingTransition(R.anim.slide_from_right,
|
||||
R.anim.slide_to_left)
|
||||
activity?.overridePendingTransition(
|
||||
R.anim.slide_from_right,
|
||||
R.anim.slide_to_left
|
||||
)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
@ -116,8 +131,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
setOnPreferenceClickListener {
|
||||
val intent = Intent(context, InstanceListActivity::class.java)
|
||||
activity?.startActivity(intent)
|
||||
activity?.overridePendingTransition(R.anim.slide_from_right,
|
||||
R.anim.slide_to_left)
|
||||
activity?.overridePendingTransition(
|
||||
R.anim.slide_from_right,
|
||||
R.anim.slide_to_left
|
||||
)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
@ -130,7 +147,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
key = PrefKeys.DEFAULT_POST_PRIVACY
|
||||
setSummaryProvider { entry }
|
||||
val visibility = accountManager.activeAccount?.defaultPostPrivacy
|
||||
?: Status.Visibility.PUBLIC
|
||||
?: Status.Visibility.PUBLIC
|
||||
value = visibility.serverString()
|
||||
setIcon(getIconForVisibility(visibility))
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
|
@ -147,7 +164,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
key = PrefKeys.DEFAULT_MEDIA_SENSITIVITY
|
||||
isSingleLineTitle = false
|
||||
val sensitivity = accountManager.activeAccount?.defaultMediaSensitivity
|
||||
?: false
|
||||
?: false
|
||||
setDefaultValue(sensitivity)
|
||||
setIcon(getIconForSensitivity(sensitivity))
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
|
@ -201,8 +218,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
preference {
|
||||
setTitle(R.string.pref_title_public_filter_keywords)
|
||||
setOnPreferenceClickListener {
|
||||
launchFilterActivity(Filter.PUBLIC,
|
||||
R.string.pref_title_public_filter_keywords)
|
||||
launchFilterActivity(
|
||||
Filter.PUBLIC,
|
||||
R.string.pref_title_public_filter_keywords
|
||||
)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
@ -226,8 +245,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
preference {
|
||||
setTitle(R.string.pref_title_thread_filter_keywords)
|
||||
setOnPreferenceClickListener {
|
||||
launchFilterActivity(Filter.THREAD,
|
||||
R.string.pref_title_thread_filter_keywords)
|
||||
launchFilterActivity(
|
||||
Filter.THREAD,
|
||||
R.string.pref_title_thread_filter_keywords
|
||||
)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
@ -255,7 +276,6 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
it.startActivity(intent)
|
||||
it.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,36 +288,35 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
|
||||
private fun syncWithServer(visibility: String? = null, sensitive: Boolean? = null) {
|
||||
mastodonApi.accountUpdateSource(visibility, sensitive)
|
||||
.enqueue(object : Callback<Account> {
|
||||
override fun onResponse(call: Call<Account>, response: Response<Account>) {
|
||||
val account = response.body()
|
||||
if (response.isSuccessful && account != null) {
|
||||
.enqueue(object : Callback<Account> {
|
||||
override fun onResponse(call: Call<Account>, response: Response<Account>) {
|
||||
val account = response.body()
|
||||
if (response.isSuccessful && account != null) {
|
||||
|
||||
accountManager.activeAccount?.let {
|
||||
it.defaultPostPrivacy = account.source?.privacy
|
||||
?: Status.Visibility.PUBLIC
|
||||
it.defaultMediaSensitivity = account.source?.sensitive ?: false
|
||||
accountManager.saveAccount(it)
|
||||
}
|
||||
} else {
|
||||
Log.e("AccountPreferences", "failed updating settings on server")
|
||||
showErrorSnackbar(visibility, sensitive)
|
||||
accountManager.activeAccount?.let {
|
||||
it.defaultPostPrivacy = account.source?.privacy
|
||||
?: Status.Visibility.PUBLIC
|
||||
it.defaultMediaSensitivity = account.source?.sensitive ?: false
|
||||
accountManager.saveAccount(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
Log.e("AccountPreferences", "failed updating settings on server", t)
|
||||
} else {
|
||||
Log.e("AccountPreferences", "failed updating settings on server")
|
||||
showErrorSnackbar(visibility, sensitive)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
Log.e("AccountPreferences", "failed updating settings on server", t)
|
||||
showErrorSnackbar(visibility, sensitive)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun showErrorSnackbar(visibility: String?, sensitive: Boolean?) {
|
||||
view?.let { view ->
|
||||
Snackbar.make(view, R.string.pref_failed_to_sync, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry) { syncWithServer(visibility, sensitive) }
|
||||
.show()
|
||||
.setAction(R.string.action_retry) { syncWithServer(visibility, sensitive) }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@ import kotlin.system.exitProcess
|
|||
* This Preference lets the user select their preferred emoji font
|
||||
*/
|
||||
class EmojiPreference(
|
||||
context: Context,
|
||||
private val okHttpClient: OkHttpClient
|
||||
context: Context,
|
||||
private val okHttpClient: OkHttpClient
|
||||
) : Preference(context) {
|
||||
|
||||
private lateinit var selected: EmojiCompatFont
|
||||
|
@ -51,7 +51,7 @@ class EmojiPreference(
|
|||
|
||||
// Find out which font is currently active
|
||||
selected = EmojiCompatFont.byId(
|
||||
PreferenceManager.getDefaultSharedPreferences(context).getInt(key, 0)
|
||||
PreferenceManager.getDefaultSharedPreferences(context).getInt(key, 0)
|
||||
)
|
||||
// We'll use this later to determine if anything has changed
|
||||
original = selected
|
||||
|
@ -67,10 +67,10 @@ class EmojiPreference(
|
|||
setupItem(SYSTEM_DEFAULT, binding.itemNomoji)
|
||||
|
||||
AlertDialog.Builder(context)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> onDialogOk() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> onDialogOk() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun setupItem(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
|
||||
|
@ -100,32 +100,30 @@ class EmojiPreference(
|
|||
binding.emojiProgress.progress = 0
|
||||
binding.emojiDownloadCancel.show()
|
||||
font.downloadFontFile(context, okHttpClient)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ progress ->
|
||||
// The progress is returned as a float between 0 and 1, or -1 if it could not determined
|
||||
if (progress >= 0) {
|
||||
binding.emojiProgress.isIndeterminate = false
|
||||
val max = binding.emojiProgress.max.toFloat()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
binding.emojiProgress.setProgress((max * progress).toInt(), true)
|
||||
} else {
|
||||
binding.emojiProgress.progress = (max * progress).toInt()
|
||||
}
|
||||
} else {
|
||||
binding.emojiProgress.isIndeterminate = true
|
||||
}
|
||||
},
|
||||
{
|
||||
Toast.makeText(context, R.string.download_failed, Toast.LENGTH_SHORT).show()
|
||||
updateItem(font, binding)
|
||||
},
|
||||
{
|
||||
finishDownload(font, binding)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ progress ->
|
||||
// The progress is returned as a float between 0 and 1, or -1 if it could not determined
|
||||
if (progress >= 0) {
|
||||
binding.emojiProgress.isIndeterminate = false
|
||||
val max = binding.emojiProgress.max.toFloat()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
binding.emojiProgress.setProgress((max * progress).toInt(), true)
|
||||
} else {
|
||||
binding.emojiProgress.progress = (max * progress).toInt()
|
||||
}
|
||||
).also { downloadDisposables[font.id] = it }
|
||||
|
||||
|
||||
} else {
|
||||
binding.emojiProgress.isIndeterminate = true
|
||||
}
|
||||
},
|
||||
{
|
||||
Toast.makeText(context, R.string.download_failed, Toast.LENGTH_SHORT).show()
|
||||
updateItem(font, binding)
|
||||
},
|
||||
{
|
||||
finishDownload(font, binding)
|
||||
}
|
||||
).also { downloadDisposables[font.id] = it }
|
||||
}
|
||||
|
||||
private fun cancelDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
|
||||
|
@ -197,10 +195,10 @@ class EmojiPreference(
|
|||
val index = selected.id
|
||||
Log.i(TAG, "saveSelectedFont: Font ID: $index")
|
||||
PreferenceManager
|
||||
.getDefaultSharedPreferences(context)
|
||||
.edit()
|
||||
.putInt(key, index)
|
||||
.apply()
|
||||
.getDefaultSharedPreferences(context)
|
||||
.edit()
|
||||
.putInt(key, index)
|
||||
.apply()
|
||||
summary = selected.getDisplay(context)
|
||||
}
|
||||
|
||||
|
@ -211,29 +209,31 @@ class EmojiPreference(
|
|||
saveSelectedFont()
|
||||
if (selected !== original || updated) {
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(R.string.restart_required)
|
||||
.setMessage(R.string.restart_emoji)
|
||||
.setNegativeButton(R.string.later, null)
|
||||
.setPositiveButton(R.string.restart) { _, _ ->
|
||||
// Restart the app
|
||||
// From https://stackoverflow.com/a/17166729/5070653
|
||||
val launchIntent = Intent(context, SplashActivity::class.java)
|
||||
val mPendingIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
0x1f973, // This is the codepoint of the party face emoji :D
|
||||
launchIntent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT)
|
||||
val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
mgr.set(
|
||||
AlarmManager.RTC,
|
||||
System.currentTimeMillis() + 100,
|
||||
mPendingIntent)
|
||||
exitProcess(0)
|
||||
}.show()
|
||||
.setTitle(R.string.restart_required)
|
||||
.setMessage(R.string.restart_emoji)
|
||||
.setNegativeButton(R.string.later, null)
|
||||
.setPositiveButton(R.string.restart) { _, _ ->
|
||||
// Restart the app
|
||||
// From https://stackoverflow.com/a/17166729/5070653
|
||||
val launchIntent = Intent(context, SplashActivity::class.java)
|
||||
val mPendingIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
0x1f973, // This is the codepoint of the party face emoji :D
|
||||
launchIntent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT
|
||||
)
|
||||
val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
mgr.set(
|
||||
AlarmManager.RTC,
|
||||
System.currentTimeMillis() + 100,
|
||||
mPendingIntent
|
||||
)
|
||||
exitProcess(0)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "EmojiPreference"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ class NotificationPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
switchPreference {
|
||||
setTitle(R.string.pref_title_notification_filter_subscriptions)
|
||||
key = PrefKeys.NOTIFICATION_FILTER_SUBSCRIPTIONS
|
||||
|
@ -176,5 +176,4 @@ class NotificationPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
return NotificationPreferencesFragment()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,8 +36,10 @@ import dagger.android.DispatchingAndroidInjector
|
|||
import dagger.android.HasAndroidInjector
|
||||
import javax.inject.Inject
|
||||
|
||||
class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
HasAndroidInjector {
|
||||
class PreferencesActivity :
|
||||
BaseActivity(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
HasAndroidInjector {
|
||||
|
||||
@Inject
|
||||
lateinit var eventHub: EventHub
|
||||
|
@ -62,36 +64,35 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference
|
|||
val fragmentTag = "preference_fragment_$EXTRA_PREFERENCE_TYPE"
|
||||
|
||||
val fragment: Fragment = supportFragmentManager.findFragmentByTag(fragmentTag)
|
||||
?: when (intent.getIntExtra(EXTRA_PREFERENCE_TYPE, 0)) {
|
||||
GENERAL_PREFERENCES -> {
|
||||
setTitle(R.string.action_view_preferences)
|
||||
PreferencesFragment.newInstance()
|
||||
}
|
||||
ACCOUNT_PREFERENCES -> {
|
||||
setTitle(R.string.action_view_account_preferences)
|
||||
AccountPreferencesFragment.newInstance()
|
||||
}
|
||||
NOTIFICATION_PREFERENCES -> {
|
||||
setTitle(R.string.pref_title_edit_notification_settings)
|
||||
NotificationPreferencesFragment.newInstance()
|
||||
}
|
||||
TAB_FILTER_PREFERENCES -> {
|
||||
setTitle(R.string.pref_title_status_tabs)
|
||||
TabFilterPreferencesFragment.newInstance()
|
||||
}
|
||||
PROXY_PREFERENCES -> {
|
||||
setTitle(R.string.pref_title_http_proxy_settings)
|
||||
ProxyPreferencesFragment.newInstance()
|
||||
}
|
||||
else -> throw IllegalArgumentException("preferenceType not known")
|
||||
?: when (intent.getIntExtra(EXTRA_PREFERENCE_TYPE, 0)) {
|
||||
GENERAL_PREFERENCES -> {
|
||||
setTitle(R.string.action_view_preferences)
|
||||
PreferencesFragment.newInstance()
|
||||
}
|
||||
ACCOUNT_PREFERENCES -> {
|
||||
setTitle(R.string.action_view_account_preferences)
|
||||
AccountPreferencesFragment.newInstance()
|
||||
}
|
||||
NOTIFICATION_PREFERENCES -> {
|
||||
setTitle(R.string.pref_title_edit_notification_settings)
|
||||
NotificationPreferencesFragment.newInstance()
|
||||
}
|
||||
TAB_FILTER_PREFERENCES -> {
|
||||
setTitle(R.string.pref_title_status_tabs)
|
||||
TabFilterPreferencesFragment.newInstance()
|
||||
}
|
||||
PROXY_PREFERENCES -> {
|
||||
setTitle(R.string.pref_title_http_proxy_settings)
|
||||
ProxyPreferencesFragment.newInstance()
|
||||
}
|
||||
else -> throw IllegalArgumentException("preferenceType not known")
|
||||
}
|
||||
|
||||
supportFragmentManager.commit {
|
||||
replace(R.id.fragment_container, fragment, fragmentTag)
|
||||
}
|
||||
|
||||
restartActivitiesOnExit = intent.getBooleanExtra("restart", false)
|
||||
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -122,7 +123,6 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference
|
|||
|
||||
restartActivitiesOnExit = true
|
||||
this.restartCurrentActivity()
|
||||
|
||||
}
|
||||
"statusTextSize", "absoluteTimeView", "showBotOverlay", "animateGifAvatars",
|
||||
"useBlurhash", "showCardsInTimelines", "confirmReblogs", "enableSwipeForTabs", "mainNavPosition", PrefKeys.HIDE_TOP_TOOLBAR -> {
|
||||
|
@ -179,5 +179,4 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference
|
|||
return intent
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,14 @@ import com.keylesspalace.tusky.R
|
|||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.entity.Notification
|
||||
import com.keylesspalace.tusky.settings.*
|
||||
import com.keylesspalace.tusky.settings.AppTheme
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.settings.emojiPreference
|
||||
import com.keylesspalace.tusky.settings.listPreference
|
||||
import com.keylesspalace.tusky.settings.makePreferenceScreen
|
||||
import com.keylesspalace.tusky.settings.preference
|
||||
import com.keylesspalace.tusky.settings.preferenceCategory
|
||||
import com.keylesspalace.tusky.settings.switchPreference
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.deserialize
|
||||
import com.keylesspalace.tusky.util.getNonNullString
|
||||
|
@ -122,7 +129,6 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
setTitle(R.string.pref_title_bot_overlay)
|
||||
isSingleLineTitle = false
|
||||
setIcon(R.drawable.ic_bot_24dp)
|
||||
|
||||
}
|
||||
|
||||
switchPreference {
|
||||
|
@ -259,7 +265,6 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
sizePx = iconSize
|
||||
colorInt = ThemeUtils.getColor(context, R.attr.iconColor)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -274,7 +279,7 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
|
||||
try {
|
||||
val httpPort = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_PORT, "-1")
|
||||
.toInt()
|
||||
.toInt()
|
||||
|
||||
if (httpProxyEnabled && httpServer.isNotBlank() && httpPort > 0 && httpPort < 65535) {
|
||||
httpProxyPref?.summary = "$httpServer:$httpPort"
|
||||
|
|
|
@ -50,7 +50,6 @@ class ProxyPreferencesFragment : PreferenceFragmentCompat() {
|
|||
setSummaryProvider { text }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
|
|
@ -126,12 +126,12 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
|
||||
@JvmStatic
|
||||
fun getIntent(context: Context, accountId: String, userName: String, statusId: String? = null) =
|
||||
Intent(context, ReportActivity::class.java)
|
||||
.apply {
|
||||
putExtra(ACCOUNT_ID, accountId)
|
||||
putExtra(ACCOUNT_USERNAME, userName)
|
||||
putExtra(STATUS_ID, statusId)
|
||||
}
|
||||
Intent(context, ReportActivity::class.java)
|
||||
.apply {
|
||||
putExtra(ACCOUNT_ID, accountId)
|
||||
putExtra(ACCOUNT_USERNAME, userName)
|
||||
putExtra(STATUS_ID, statusId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun androidInjector() = androidInjector
|
||||
|
|
|
@ -43,8 +43,8 @@ import kotlinx.coroutines.launch
|
|||
import javax.inject.Inject
|
||||
|
||||
class ReportViewModel @Inject constructor(
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val eventHub: EventHub
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val eventHub: EventHub
|
||||
) : RxAwareViewModel() {
|
||||
|
||||
private val navigationMutable = MutableLiveData<Screen?>()
|
||||
|
@ -121,18 +121,17 @@ class ReportViewModel @Inject constructor(
|
|||
muteStateMutable.value = Loading()
|
||||
blockStateMutable.value = Loading()
|
||||
mastodonApi.relationships(ids)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ data ->
|
||||
updateRelationship(data.getOrNull(0))
|
||||
|
||||
},
|
||||
{
|
||||
updateRelationship(null)
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ data ->
|
||||
updateRelationship(data.getOrNull(0))
|
||||
},
|
||||
{
|
||||
updateRelationship(null)
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
}
|
||||
|
||||
private fun updateRelationship(relationship: Relationship?) {
|
||||
|
@ -152,20 +151,20 @@ class ReportViewModel @Inject constructor(
|
|||
} else {
|
||||
mastodonApi.muteAccount(accountId)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ relationship ->
|
||||
val muting = relationship?.muting == true
|
||||
muteStateMutable.value = Success(muting)
|
||||
if (muting) {
|
||||
eventHub.dispatch(MuteEvent(accountId))
|
||||
}
|
||||
},
|
||||
{ error ->
|
||||
muteStateMutable.value = Error(false, error.message)
|
||||
}
|
||||
).autoDispose()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ relationship ->
|
||||
val muting = relationship?.muting == true
|
||||
muteStateMutable.value = Success(muting)
|
||||
if (muting) {
|
||||
eventHub.dispatch(MuteEvent(accountId))
|
||||
}
|
||||
},
|
||||
{ error ->
|
||||
muteStateMutable.value = Error(false, error.message)
|
||||
}
|
||||
).autoDispose()
|
||||
|
||||
muteStateMutable.value = Loading()
|
||||
}
|
||||
|
@ -177,21 +176,21 @@ class ReportViewModel @Inject constructor(
|
|||
} else {
|
||||
mastodonApi.blockAccount(accountId)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ relationship ->
|
||||
val blocking = relationship?.blocking == true
|
||||
blockStateMutable.value = Success(blocking)
|
||||
if (blocking) {
|
||||
eventHub.dispatch(BlockEvent(accountId))
|
||||
}
|
||||
},
|
||||
{ error ->
|
||||
blockStateMutable.value = Error(false, error.message)
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ relationship ->
|
||||
val blocking = relationship?.blocking == true
|
||||
blockStateMutable.value = Success(blocking)
|
||||
if (blocking) {
|
||||
eventHub.dispatch(BlockEvent(accountId))
|
||||
}
|
||||
},
|
||||
{ error ->
|
||||
blockStateMutable.value = Error(false, error.message)
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
|
||||
blockStateMutable.value = Loading()
|
||||
}
|
||||
|
@ -199,18 +198,17 @@ class ReportViewModel @Inject constructor(
|
|||
fun doReport() {
|
||||
reportingStateMutable.value = Loading()
|
||||
mastodonApi.reportObservable(accountId, selectedIds.toList(), reportNote, if (isRemoteAccount) isRemoteNotify else null)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
reportingStateMutable.value = Success(true)
|
||||
},
|
||||
{ error ->
|
||||
reportingStateMutable.value = Error(cause = error)
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
reportingStateMutable.value = Success(true)
|
||||
},
|
||||
{ error ->
|
||||
reportingStateMutable.value = Error(cause = error)
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
}
|
||||
|
||||
fun checkClickedUrl(url: String?) {
|
||||
|
|
|
@ -21,4 +21,4 @@ enum class Screen {
|
|||
Done,
|
||||
Back,
|
||||
Finish
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,8 @@ import android.view.View
|
|||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
|
||||
interface AdapterHandler: LinkListener {
|
||||
interface AdapterHandler : LinkListener {
|
||||
fun showMedia(v: View?, status: Status?, idx: Int)
|
||||
fun setStatusChecked(status: Status, isChecked: Boolean)
|
||||
fun isStatusChecked(id: String): Boolean
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,4 +33,4 @@ class ReportPagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(acti
|
|||
}
|
||||
|
||||
override fun getItemCount() = 3
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,18 +25,25 @@ import com.keylesspalace.tusky.databinding.ItemReportStatusBinding
|
|||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
import com.keylesspalace.tusky.util.StatusViewHelper
|
||||
import com.keylesspalace.tusky.util.StatusViewHelper.Companion.COLLAPSE_INPUT_FILTER
|
||||
import com.keylesspalace.tusky.util.StatusViewHelper.Companion.NO_INPUT_FILTER
|
||||
import com.keylesspalace.tusky.util.TimestampUtils
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.shouldTrimStatus
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.viewdata.toViewData
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
|
||||
class StatusViewHolder(
|
||||
private val binding: ItemReportStatusBinding,
|
||||
private val statusDisplayOptions: StatusDisplayOptions,
|
||||
private val viewState: StatusViewState,
|
||||
private val adapterHandler: AdapterHandler,
|
||||
private val getStatusForPosition: (Int) -> Status?
|
||||
private val binding: ItemReportStatusBinding,
|
||||
private val statusDisplayOptions: StatusDisplayOptions,
|
||||
private val viewState: StatusViewState,
|
||||
private val adapterHandler: AdapterHandler,
|
||||
private val getStatusForPosition: (Int) -> Status?
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
private val mediaViewHeight = itemView.context.resources.getDimensionPixelSize(R.dimen.status_media_preview_height)
|
||||
private val statusViewHelper = StatusViewHelper(itemView)
|
||||
|
@ -71,9 +78,11 @@ class StatusViewHolder(
|
|||
|
||||
val sensitive = status.sensitive
|
||||
|
||||
statusViewHelper.setMediasPreview(statusDisplayOptions, status.attachments,
|
||||
sensitive, previewListener, viewState.isMediaShow(status.id, status.sensitive),
|
||||
mediaViewHeight)
|
||||
statusViewHelper.setMediasPreview(
|
||||
statusDisplayOptions, status.attachments,
|
||||
sensitive, previewListener, viewState.isMediaShow(status.id, status.sensitive),
|
||||
mediaViewHeight
|
||||
)
|
||||
|
||||
statusViewHelper.setupPollReadonly(status.poll.toViewData(), status.emojis, statusDisplayOptions)
|
||||
setCreatedAt(status.createdAt)
|
||||
|
@ -81,8 +90,10 @@ class StatusViewHolder(
|
|||
|
||||
private fun updateTextView() {
|
||||
status()?.let { status ->
|
||||
setupCollapsedState(shouldTrimStatus(status.content), viewState.isCollapsed(status.id, true),
|
||||
viewState.isContentShow(status.id, status.sensitive), status.spoilerText)
|
||||
setupCollapsedState(
|
||||
shouldTrimStatus(status.content), viewState.isCollapsed(status.id, true),
|
||||
viewState.isContentShow(status.id, status.sensitive), status.spoilerText
|
||||
)
|
||||
|
||||
if (status.spoilerText.isBlank()) {
|
||||
setTextVisible(true, status.content, status.mentions, status.emojis, adapterHandler)
|
||||
|
@ -109,18 +120,20 @@ class StatusViewHolder(
|
|||
}
|
||||
|
||||
private fun setContentWarningButtonText(contentShown: Boolean) {
|
||||
if(contentShown) {
|
||||
if (contentShown) {
|
||||
binding.statusContentWarningButton.setText(R.string.status_content_warning_show_less)
|
||||
} else {
|
||||
binding.statusContentWarningButton.setText(R.string.status_content_warning_show_more)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setTextVisible(expanded: Boolean,
|
||||
content: Spanned,
|
||||
mentions: List<Status.Mention>?,
|
||||
emojis: List<Emoji>,
|
||||
listener: LinkListener) {
|
||||
private fun setTextVisible(
|
||||
expanded: Boolean,
|
||||
content: Spanned,
|
||||
mentions: List<Status.Mention>?,
|
||||
emojis: List<Emoji>,
|
||||
listener: LinkListener
|
||||
) {
|
||||
if (expanded) {
|
||||
val emojifiedText = content.emojify(emojis, binding.statusContent, statusDisplayOptions.animateEmojis)
|
||||
LinkHelper.setClickableText(binding.statusContent, emojifiedText, mentions, listener)
|
||||
|
@ -152,7 +165,7 @@ class StatusViewHolder(
|
|||
private fun setupCollapsedState(collapsible: Boolean, collapsed: Boolean, expanded: Boolean, spoilerText: String) {
|
||||
/* input filter for TextViews have to be set before text */
|
||||
if (collapsible && (expanded || TextUtils.isEmpty(spoilerText))) {
|
||||
binding.buttonToggleContent.setOnClickListener{
|
||||
binding.buttonToggleContent.setOnClickListener {
|
||||
status()?.let { status ->
|
||||
viewState.setCollapsed(status.id, !collapsed)
|
||||
updateTextView()
|
||||
|
@ -174,4 +187,4 @@ class StatusViewHolder(
|
|||
}
|
||||
|
||||
private fun status() = getStatusForPosition(bindingAdapterPosition)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,9 @@ import com.keylesspalace.tusky.entity.Status
|
|||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
|
||||
class StatusesAdapter(
|
||||
private val statusDisplayOptions: StatusDisplayOptions,
|
||||
private val statusViewState: StatusViewState,
|
||||
private val adapterHandler: AdapterHandler
|
||||
private val statusDisplayOptions: StatusDisplayOptions,
|
||||
private val statusViewState: StatusViewState,
|
||||
private val adapterHandler: AdapterHandler
|
||||
) : PagingDataAdapter<Status, StatusViewHolder>(STATUS_COMPARATOR) {
|
||||
|
||||
private val statusForPosition: (Int) -> Status? = { position: Int ->
|
||||
|
@ -37,8 +37,10 @@ class StatusesAdapter(
|
|||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StatusViewHolder {
|
||||
val binding = ItemReportStatusBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return StatusViewHolder(binding, statusDisplayOptions, statusViewState, adapterHandler,
|
||||
statusForPosition)
|
||||
return StatusViewHolder(
|
||||
binding, statusDisplayOptions, statusViewState, adapterHandler,
|
||||
statusForPosition
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: StatusViewHolder, position: Int) {
|
||||
|
@ -50,10 +52,10 @@ class StatusesAdapter(
|
|||
companion object {
|
||||
val STATUS_COMPARATOR = object : DiffUtil.ItemCallback<Status>() {
|
||||
override fun areContentsTheSame(oldItem: Status, newItem: Status): Boolean =
|
||||
oldItem == newItem
|
||||
oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(oldItem: Status, newItem: Status): Boolean =
|
||||
oldItem.id == newItem.id
|
||||
oldItem.id == newItem.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class StatusesPagingSource(
|
|||
|
||||
override fun getRefreshKey(state: PagingState<String, Status>): String? {
|
||||
return state.anchorPosition?.let { anchorPosition ->
|
||||
state.closestItemToPosition(anchorPosition)?.id
|
||||
state.closestItemToPosition(anchorPosition)?.id
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,6 @@ class StatusesPagingSource(
|
|||
prevKey = result.firstOrNull()?.id,
|
||||
nextKey = result.lastOrNull()?.id
|
||||
)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.w("StatusesPagingSource", "failed to load statuses", e)
|
||||
return LoadResult.Error(e)
|
||||
|
@ -86,4 +85,4 @@ class StatusesPagingSource(
|
|||
excludeReblogs = true
|
||||
).await()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,27 +56,29 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
|||
binding.progressMute.hide()
|
||||
}
|
||||
|
||||
binding.buttonMute.setText(when (it.data) {
|
||||
true -> R.string.action_unmute
|
||||
else -> R.string.action_mute
|
||||
})
|
||||
binding.buttonMute.setText(
|
||||
when (it.data) {
|
||||
true -> R.string.action_unmute
|
||||
else -> R.string.action_mute
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
viewModel.blockState.observe(viewLifecycleOwner) {
|
||||
if (it !is Loading) {
|
||||
binding.buttonBlock.show()
|
||||
binding.progressBlock.show()
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
binding.buttonBlock.hide()
|
||||
binding.progressBlock.hide()
|
||||
}
|
||||
binding.buttonBlock.setText(when (it.data) {
|
||||
true -> R.string.action_unblock
|
||||
else -> R.string.action_block
|
||||
})
|
||||
binding.buttonBlock.setText(
|
||||
when (it.data) {
|
||||
true -> R.string.action_unblock
|
||||
else -> R.string.action_block
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun handleClicks() {
|
||||
|
|
|
@ -64,11 +64,10 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
|||
private fun fillViews() {
|
||||
binding.editNote.setText(viewModel.reportNote)
|
||||
|
||||
if (viewModel.isRemoteAccount){
|
||||
if (viewModel.isRemoteAccount) {
|
||||
binding.checkIsNotifyRemote.show()
|
||||
binding.reportDescriptionRemoteInstance.show()
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
binding.checkIsNotifyRemote.hide()
|
||||
binding.reportDescriptionRemoteInstance.hide()
|
||||
}
|
||||
|
@ -84,7 +83,6 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
|||
is Success -> viewModel.navigateTo(Screen.Done)
|
||||
is Loading -> showLoading()
|
||||
is Error -> showError(it.cause)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,15 +107,15 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
|||
private fun initStatusesView() {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val statusDisplayOptions = StatusDisplayOptions(
|
||||
animateAvatars = false,
|
||||
mediaPreviewEnabled = accountManager.activeAccount?.mediaPreviewEnabled ?: true,
|
||||
useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false),
|
||||
showBotOverlay = false,
|
||||
useBlurhash = preferences.getBoolean("useBlurhash", true),
|
||||
cardViewMode = CardViewMode.NONE,
|
||||
confirmReblogs = preferences.getBoolean("confirmReblogs", true),
|
||||
hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false),
|
||||
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||
animateAvatars = false,
|
||||
mediaPreviewEnabled = accountManager.activeAccount?.mediaPreviewEnabled ?: true,
|
||||
useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false),
|
||||
showBotOverlay = false,
|
||||
useBlurhash = preferences.getBoolean("useBlurhash", true),
|
||||
cardViewMode = CardViewMode.NONE,
|
||||
confirmReblogs = preferences.getBoolean("confirmReblogs", true),
|
||||
hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false),
|
||||
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||
)
|
||||
|
||||
adapter = StatusesAdapter(statusDisplayOptions, viewModel.statusViewState, this)
|
||||
|
@ -132,9 +132,10 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
|||
}
|
||||
|
||||
adapter.addLoadStateListener { loadState ->
|
||||
if (loadState.refresh is LoadState.Error
|
||||
|| loadState.append is LoadState.Error
|
||||
|| loadState.prepend is LoadState.Error) {
|
||||
if (loadState.refresh is LoadState.Error ||
|
||||
loadState.append is LoadState.Error ||
|
||||
loadState.prepend is LoadState.Error
|
||||
) {
|
||||
showError()
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ class StatusViewState {
|
|||
fun setCollapsed(id: String, isCollapsed: Boolean) = setStateEnabled(longContentCollapsedState, id, isCollapsed)
|
||||
|
||||
private fun isStateEnabled(map: Map<String, Boolean>, id: String, def: Boolean): Boolean = map[id]
|
||||
?: def
|
||||
?: def
|
||||
|
||||
private fun setStateEnabled(map: MutableMap<String, Boolean>, id: String, state: Boolean) = map.put(id, state)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,9 +29,9 @@ import kotlinx.coroutines.rx3.await
|
|||
import javax.inject.Inject
|
||||
|
||||
class ScheduledTootViewModel @Inject constructor(
|
||||
val mastodonApi: MastodonApi,
|
||||
val eventHub: EventHub
|
||||
): ViewModel() {
|
||||
val mastodonApi: MastodonApi,
|
||||
val eventHub: EventHub
|
||||
) : ViewModel() {
|
||||
|
||||
private val pagingSourceFactory = ScheduledTootPagingSourceFactory(mastodonApi)
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ class SearchActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
|
||||
menuInflater.inflate(R.menu.search_toolbar, menu)
|
||||
val searchView = menu.findItem(R.id.action_search)
|
||||
.actionView as SearchView
|
||||
.actionView as SearchView
|
||||
setupSearchView(searchView)
|
||||
|
||||
searchView.setQuery(viewModel.currentQuery, false)
|
||||
|
|
|
@ -19,4 +19,4 @@ enum class SearchType(val apiParameter: String) {
|
|||
Status("statuses"),
|
||||
Account("accounts"),
|
||||
Hashtag("hashtags")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,13 +95,17 @@ class SearchViewModel @Inject constructor(
|
|||
|
||||
fun removeItem(status: Pair<Status, StatusViewData.Concrete>) {
|
||||
timelineCases.delete(status.first.id)
|
||||
.subscribe({
|
||||
.subscribe(
|
||||
{
|
||||
if (loadedStatuses.remove(status))
|
||||
statusesPagingSourceFactory.invalidate()
|
||||
}, {
|
||||
err -> Log.d(TAG, "Failed to delete status", err)
|
||||
})
|
||||
.autoDispose()
|
||||
},
|
||||
{
|
||||
err ->
|
||||
Log.d(TAG, "Failed to delete status", err)
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
}
|
||||
|
||||
fun expandedChange(status: Pair<Status, StatusViewData.Concrete>, expanded: Boolean) {
|
||||
|
@ -225,4 +229,4 @@ class SearchViewModel @Inject constructor(
|
|||
private const val TAG = "SearchViewModel"
|
||||
private const val DEFAULT_LOAD_SIZE = 20
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,12 +24,12 @@ import com.keylesspalace.tusky.adapter.AccountViewHolder
|
|||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
|
||||
class SearchAccountsAdapter(private val linkListener: LinkListener, private val animateAvatars: Boolean, private val animateEmojis: Boolean)
|
||||
: PagingDataAdapter<Account, AccountViewHolder>(ACCOUNT_COMPARATOR) {
|
||||
class SearchAccountsAdapter(private val linkListener: LinkListener, private val animateAvatars: Boolean, private val animateEmojis: Boolean) :
|
||||
PagingDataAdapter<Account, AccountViewHolder>(ACCOUNT_COMPARATOR) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_account, parent, false)
|
||||
.inflate(R.layout.item_account, parent, false)
|
||||
return AccountViewHolder(view)
|
||||
}
|
||||
|
||||
|
@ -46,10 +46,10 @@ class SearchAccountsAdapter(private val linkListener: LinkListener, private val
|
|||
|
||||
val ACCOUNT_COMPARATOR = object : DiffUtil.ItemCallback<Account>() {
|
||||
override fun areContentsTheSame(oldItem: Account, newItem: Account): Boolean =
|
||||
oldItem.deepEquals(newItem)
|
||||
oldItem.deepEquals(newItem)
|
||||
|
||||
override fun areItemsTheSame(oldItem: Account, newItem: Account): Boolean =
|
||||
oldItem.id == newItem.id
|
||||
oldItem.id == newItem.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,8 +24,8 @@ import com.keylesspalace.tusky.entity.HashTag
|
|||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
|
||||
class SearchHashtagsAdapter(private val linkListener: LinkListener)
|
||||
: PagingDataAdapter<HashTag, BindingHolder<ItemHashtagBinding>>(HASHTAG_COMPARATOR) {
|
||||
class SearchHashtagsAdapter(private val linkListener: LinkListener) :
|
||||
PagingDataAdapter<HashTag, BindingHolder<ItemHashtagBinding>>(HASHTAG_COMPARATOR) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemHashtagBinding> {
|
||||
val binding = ItemHashtagBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
|
@ -43,10 +43,10 @@ class SearchHashtagsAdapter(private val linkListener: LinkListener)
|
|||
|
||||
val HASHTAG_COMPARATOR = object : DiffUtil.ItemCallback<HashTag>() {
|
||||
override fun areContentsTheSame(oldItem: HashTag, newItem: HashTag): Boolean =
|
||||
oldItem.name == newItem.name
|
||||
oldItem.name == newItem.name
|
||||
|
||||
override fun areItemsTheSame(oldItem: HashTag, newItem: HashTag): Boolean =
|
||||
oldItem.name == newItem.name
|
||||
oldItem.name == newItem.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,5 +34,4 @@ class SearchPagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(acti
|
|||
}
|
||||
|
||||
override fun getItemCount() = 3
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,12 +22,13 @@ import com.keylesspalace.tusky.entity.SearchResult
|
|||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import kotlinx.coroutines.rx3.await
|
||||
|
||||
class SearchPagingSource<T: Any>(
|
||||
class SearchPagingSource<T : Any>(
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val searchType: SearchType,
|
||||
private val searchRequest: String,
|
||||
private val initialItems: List<T>?,
|
||||
private val parser: (SearchResult) -> List<T>) : PagingSource<Int, T>() {
|
||||
private val parser: (SearchResult) -> List<T>
|
||||
) : PagingSource<Int, T>() {
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Int, T>): Int? {
|
||||
return null
|
||||
|
@ -80,4 +81,4 @@ class SearchPagingSource<T: Any>(
|
|||
return LoadResult.Error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,4 +50,4 @@ class SearchPagingSourceFactory<T : Any>(
|
|||
fun invalidate() {
|
||||
currentSource?.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,9 +28,9 @@ class SearchAccountsFragment : SearchFragment<Account>() {
|
|||
val preferences = PreferenceManager.getDefaultSharedPreferences(binding.searchRecyclerView.context)
|
||||
|
||||
return SearchAccountsAdapter(
|
||||
this,
|
||||
preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),
|
||||
preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||
this,
|
||||
preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),
|
||||
preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -29,8 +29,11 @@ import kotlinx.coroutines.flow.collectLatest
|
|||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class SearchFragment<T: Any> : Fragment(R.layout.fragment_search),
|
||||
LinkListener, Injectable, SwipeRefreshLayout.OnRefreshListener {
|
||||
abstract class SearchFragment<T : Any> :
|
||||
Fragment(R.layout.fragment_search),
|
||||
LinkListener,
|
||||
Injectable,
|
||||
SwipeRefreshLayout.OnRefreshListener {
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
|
|
@ -74,15 +74,15 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
override fun createAdapter(): PagingDataAdapter<Pair<Status, StatusViewData.Concrete>, *> {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(binding.searchRecyclerView.context)
|
||||
val statusDisplayOptions = StatusDisplayOptions(
|
||||
animateAvatars = preferences.getBoolean("animateGifAvatars", false),
|
||||
mediaPreviewEnabled = viewModel.mediaPreviewEnabled,
|
||||
useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false),
|
||||
showBotOverlay = preferences.getBoolean("showBotOverlay", true),
|
||||
useBlurhash = preferences.getBoolean("useBlurhash", true),
|
||||
cardViewMode = CardViewMode.NONE,
|
||||
confirmReblogs = preferences.getBoolean("confirmReblogs", true),
|
||||
hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false),
|
||||
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||
animateAvatars = preferences.getBoolean("animateGifAvatars", false),
|
||||
mediaPreviewEnabled = viewModel.mediaPreviewEnabled,
|
||||
useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false),
|
||||
showBotOverlay = preferences.getBoolean("showBotOverlay", true),
|
||||
useBlurhash = preferences.getBoolean("useBlurhash", true),
|
||||
cardViewMode = CardViewMode.NONE,
|
||||
confirmReblogs = preferences.getBoolean("confirmReblogs", true),
|
||||
hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false),
|
||||
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||
)
|
||||
|
||||
binding.searchRecyclerView.addItemDecoration(DividerItemDecoration(binding.searchRecyclerView.context, DividerItemDecoration.VERTICAL))
|
||||
|
@ -125,13 +125,17 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
when (actionable.attachments[attachmentIndex].type) {
|
||||
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
|
||||
val attachments = AttachmentViewData.list(actionable)
|
||||
val intent = ViewMediaActivity.newIntent(context, attachments,
|
||||
attachmentIndex)
|
||||
val intent = ViewMediaActivity.newIntent(
|
||||
context, attachments,
|
||||
attachmentIndex
|
||||
)
|
||||
if (view != null) {
|
||||
val url = actionable.attachments[attachmentIndex].url
|
||||
ViewCompat.setTransitionName(view, url)
|
||||
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(),
|
||||
view, url)
|
||||
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
requireActivity(),
|
||||
view, url
|
||||
)
|
||||
startActivity(intent, options.toBundle())
|
||||
} else {
|
||||
startActivity(intent)
|
||||
|
@ -198,20 +202,23 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
private fun reply(status: Status) {
|
||||
val actionableStatus = status.actionableStatus
|
||||
val mentionedUsernames = actionableStatus.mentions.map { it.username }
|
||||
.toMutableSet()
|
||||
.apply {
|
||||
add(actionableStatus.account.username)
|
||||
remove(viewModel.activeAccount?.username)
|
||||
}
|
||||
.toMutableSet()
|
||||
.apply {
|
||||
add(actionableStatus.account.username)
|
||||
remove(viewModel.activeAccount?.username)
|
||||
}
|
||||
|
||||
val intent = ComposeActivity.startIntent(requireContext(), ComposeOptions(
|
||||
val intent = ComposeActivity.startIntent(
|
||||
requireContext(),
|
||||
ComposeOptions(
|
||||
inReplyToId = status.actionableId,
|
||||
replyVisibility = actionableStatus.visibility,
|
||||
contentWarning = actionableStatus.spoilerText,
|
||||
mentionedUsernames = mentionedUsernames,
|
||||
replyingStatusAuthor = actionableStatus.account.localUsername,
|
||||
replyingStatusContent = actionableStatus.content.toString()
|
||||
))
|
||||
)
|
||||
)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
|
@ -244,7 +251,7 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
menu.findItem(R.id.status_unreblog_private).isVisible = reblogged
|
||||
}
|
||||
Status.Visibility.UNKNOWN, Status.Visibility.DIRECT -> {
|
||||
} //Ignore
|
||||
} // Ignore
|
||||
}
|
||||
} else {
|
||||
popup.inflate(R.menu.status_more)
|
||||
|
@ -271,11 +278,12 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
}
|
||||
if (mutable) {
|
||||
muteConversationItem.setTitle(
|
||||
if (status.muted == true) {
|
||||
R.string.action_unmute_conversation
|
||||
} else {
|
||||
R.string.action_mute_conversation
|
||||
})
|
||||
if (status.muted == true) {
|
||||
R.string.action_unmute_conversation
|
||||
} else {
|
||||
R.string.action_mute_conversation
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
popup.setOnMenuItemClickListener { item ->
|
||||
|
@ -287,8 +295,8 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
sendIntent.action = Intent.ACTION_SEND
|
||||
|
||||
val stringToShare = statusToShare.account.username +
|
||||
" - " +
|
||||
statusToShare.content.toString()
|
||||
" - " +
|
||||
statusToShare.content.toString()
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, stringToShare)
|
||||
sendIntent.type = "text/plain"
|
||||
startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_status_content_to)))
|
||||
|
@ -361,10 +369,10 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
|
||||
private fun onBlock(accountId: String, accountUsername: String) {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setMessage(getString(R.string.dialog_block_warning, accountUsername))
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.blockAccount(accountId) }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
.setMessage(getString(R.string.dialog_block_warning, accountUsername))
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.blockAccount(accountId) }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun onMute(accountId: String, accountUsername: String) {
|
||||
|
@ -383,11 +391,14 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
}
|
||||
|
||||
private fun showOpenAsDialog(statusUrl: String, dialogTitle: CharSequence) {
|
||||
bottomSheetActivity?.showAccountChooserDialog(dialogTitle, false, object : AccountSelectionListener {
|
||||
override fun onAccountSelected(account: AccountEntity) {
|
||||
openAsAccount(statusUrl, account)
|
||||
bottomSheetActivity?.showAccountChooserDialog(
|
||||
dialogTitle, false,
|
||||
object : AccountSelectionListener {
|
||||
override fun onAccountSelected(account: AccountEntity) {
|
||||
openAsAccount(statusUrl, account)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
private fun openAsAccount(statusUrl: String, account: AccountEntity) {
|
||||
|
@ -430,51 +441,56 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
private fun showConfirmDeleteDialog(id: String, position: Int) {
|
||||
context?.let {
|
||||
AlertDialog.Builder(it)
|
||||
.setMessage(R.string.dialog_delete_toot_warning)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
viewModel.deleteStatus(id)
|
||||
removeItem(position)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
.setMessage(R.string.dialog_delete_toot_warning)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
viewModel.deleteStatus(id)
|
||||
removeItem(position)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showConfirmEditDialog(id: String, position: Int, status: Status) {
|
||||
activity?.let {
|
||||
AlertDialog.Builder(it)
|
||||
.setMessage(R.string.dialog_redraft_toot_warning)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
viewModel.deleteStatus(id)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe({ deletedStatus ->
|
||||
removeItem(position)
|
||||
.setMessage(R.string.dialog_redraft_toot_warning)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
viewModel.deleteStatus(id)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe(
|
||||
{ deletedStatus ->
|
||||
removeItem(position)
|
||||
|
||||
val redraftStatus = if (deletedStatus.isEmpty()) {
|
||||
status.toDeletedStatus()
|
||||
} else {
|
||||
deletedStatus
|
||||
}
|
||||
val redraftStatus = if (deletedStatus.isEmpty()) {
|
||||
status.toDeletedStatus()
|
||||
} else {
|
||||
deletedStatus
|
||||
}
|
||||
|
||||
val intent = ComposeActivity.startIntent(requireContext(), ComposeOptions(
|
||||
tootText = redraftStatus.text ?: "",
|
||||
inReplyToId = redraftStatus.inReplyToId,
|
||||
visibility = redraftStatus.visibility,
|
||||
contentWarning = redraftStatus.spoilerText,
|
||||
mediaAttachments = redraftStatus.attachments,
|
||||
sensitive = redraftStatus.sensitive,
|
||||
poll = redraftStatus.poll?.toNewPoll(status.createdAt)
|
||||
))
|
||||
startActivity(intent)
|
||||
}, { error ->
|
||||
Log.w("SearchStatusesFragment", "error deleting status", error)
|
||||
Toast.makeText(context, R.string.error_generic, Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
val intent = ComposeActivity.startIntent(
|
||||
requireContext(),
|
||||
ComposeOptions(
|
||||
tootText = redraftStatus.text ?: "",
|
||||
inReplyToId = redraftStatus.inReplyToId,
|
||||
visibility = redraftStatus.visibility,
|
||||
contentWarning = redraftStatus.spoilerText,
|
||||
mediaAttachments = redraftStatus.attachments,
|
||||
sensitive = redraftStatus.sensitive,
|
||||
poll = redraftStatus.poll?.toNewPoll(status.createdAt)
|
||||
)
|
||||
)
|
||||
startActivity(intent)
|
||||
},
|
||||
{ error ->
|
||||
Log.w("SearchStatusesFragment", "error deleting status", error)
|
||||
Toast.makeText(context, R.string.error_generic, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,10 +25,16 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.*
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.AsyncListDiffer
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.ListUpdateCallback
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
|
||||
import at.connyduck.sparkbutton.helpers.Utils
|
||||
import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.*
|
||||
import autodispose2.androidx.lifecycle.autoDispose
|
||||
import com.keylesspalace.tusky.AccountListActivity
|
||||
import com.keylesspalace.tusky.AccountListActivity.Companion.newIntent
|
||||
|
@ -47,7 +53,13 @@ import com.keylesspalace.tusky.interfaces.RefreshableFragment
|
|||
import com.keylesspalace.tusky.interfaces.ReselectableFragment
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.CardViewMode
|
||||
import com.keylesspalace.tusky.util.ListStatusAccessibilityDelegate
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener
|
||||
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
|
@ -56,8 +68,13 @@ import io.reactivex.rxjava3.core.Observable
|
|||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, Injectable,
|
||||
ReselectableFragment, RefreshableFragment {
|
||||
class TimelineFragment :
|
||||
SFragment(),
|
||||
OnRefreshListener,
|
||||
StatusActionListener,
|
||||
Injectable,
|
||||
ReselectableFragment,
|
||||
RefreshableFragment {
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
@ -161,8 +178,7 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I
|
|||
|
||||
private fun setupRecyclerView() {
|
||||
binding.recyclerView.setAccessibilityDelegateCompat(
|
||||
ListStatusAccessibilityDelegate(binding.recyclerView, this)
|
||||
{ pos -> viewModel.statuses.getOrNull(pos) }
|
||||
ListStatusAccessibilityDelegate(binding.recyclerView, this) { pos -> viewModel.statuses.getOrNull(pos) }
|
||||
)
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
|
@ -330,8 +346,10 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I
|
|||
}
|
||||
|
||||
override fun onViewAccount(id: String) {
|
||||
if ((viewModel.kind == TimelineViewModel.Kind.USER ||
|
||||
viewModel.kind == TimelineViewModel.Kind.USER_WITH_REPLIES) &&
|
||||
if ((
|
||||
viewModel.kind == TimelineViewModel.Kind.USER ||
|
||||
viewModel.kind == TimelineViewModel.Kind.USER_WITH_REPLIES
|
||||
) &&
|
||||
viewModel.id == id
|
||||
) {
|
||||
/* If already viewing an account page, then any requests to view that account page
|
||||
|
@ -369,9 +387,9 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I
|
|||
|
||||
private fun actionButtonPresent(): Boolean {
|
||||
return viewModel.kind != TimelineViewModel.Kind.TAG &&
|
||||
viewModel.kind != TimelineViewModel.Kind.FAVOURITES &&
|
||||
viewModel.kind != TimelineViewModel.Kind.BOOKMARKS &&
|
||||
activity is ActionButtonActivity
|
||||
viewModel.kind != TimelineViewModel.Kind.FAVOURITES &&
|
||||
viewModel.kind != TimelineViewModel.Kind.BOOKMARKS &&
|
||||
activity is ActionButtonActivity
|
||||
}
|
||||
|
||||
private fun updateViews() {
|
||||
|
@ -505,7 +523,6 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I
|
|||
private const val HASHTAGS_ARG = "hashtags"
|
||||
private const val ARG_ENABLE_SWIPE_TO_REFRESH = "enableSwipeToRefresh"
|
||||
|
||||
|
||||
fun newInstance(
|
||||
kind: TimelineViewModel.Kind,
|
||||
hashtagOrId: String? = null,
|
||||
|
@ -531,7 +548,6 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I
|
|||
return fragment
|
||||
}
|
||||
|
||||
|
||||
private val diffCallback: DiffUtil.ItemCallback<StatusViewData> =
|
||||
object : DiffUtil.ItemCallback<StatusViewData>() {
|
||||
override fun areItemsTheSame(
|
||||
|
@ -555,9 +571,9 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I
|
|||
return if (oldItem === newItem) {
|
||||
// If items are equal - update timestamp only
|
||||
listOf(StatusBaseViewHolder.Key.KEY_CREATED)
|
||||
} else // If items are different - update the whole view holder
|
||||
} else // If items are different - update the whole view holder
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,19 @@ import androidx.core.text.parseAsHtml
|
|||
import androidx.core.text.toHtml
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.keylesspalace.tusky.db.*
|
||||
import com.keylesspalace.tusky.entity.*
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.components.timeline.TimelineRequestMode.DISK
|
||||
import com.keylesspalace.tusky.components.timeline.TimelineRequestMode.NETWORK
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.db.TimelineAccountEntity
|
||||
import com.keylesspalace.tusky.db.TimelineDao
|
||||
import com.keylesspalace.tusky.db.TimelineStatusEntity
|
||||
import com.keylesspalace.tusky.db.TimelineStatusWithAccount
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.Either
|
||||
import com.keylesspalace.tusky.util.dec
|
||||
import com.keylesspalace.tusky.util.inc
|
||||
|
@ -17,9 +25,8 @@ import com.keylesspalace.tusky.util.trimTrailingWhitespace
|
|||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
data class Placeholder(val id: String)
|
||||
|
||||
|
@ -31,7 +38,10 @@ enum class TimelineRequestMode {
|
|||
|
||||
interface TimelineRepository {
|
||||
fun getStatuses(
|
||||
maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int,
|
||||
maxId: String?,
|
||||
sinceId: String?,
|
||||
sincedIdMinusOne: String?,
|
||||
limit: Int,
|
||||
requestMode: TimelineRequestMode
|
||||
): Single<out List<TimelineStatus>>
|
||||
|
||||
|
@ -52,8 +62,11 @@ class TimelineRepositoryImpl(
|
|||
}
|
||||
|
||||
override fun getStatuses(
|
||||
maxId: String?, sinceId: String?, sincedIdMinusOne: String?,
|
||||
limit: Int, requestMode: TimelineRequestMode
|
||||
maxId: String?,
|
||||
sinceId: String?,
|
||||
sincedIdMinusOne: String?,
|
||||
limit: Int,
|
||||
requestMode: TimelineRequestMode
|
||||
): Single<out List<TimelineStatus>> {
|
||||
val acc = accountManager.activeAccount ?: throw IllegalStateException()
|
||||
val accountId = acc.id
|
||||
|
@ -66,9 +79,12 @@ class TimelineRepositoryImpl(
|
|||
}
|
||||
|
||||
private fun getStatusesFromNetwork(
|
||||
maxId: String?, sinceId: String?,
|
||||
sinceIdMinusOne: String?, limit: Int,
|
||||
accountId: Long, requestMode: TimelineRequestMode
|
||||
maxId: String?,
|
||||
sinceId: String?,
|
||||
sinceIdMinusOne: String?,
|
||||
limit: Int,
|
||||
accountId: Long,
|
||||
requestMode: TimelineRequestMode
|
||||
): Single<out List<TimelineStatus>> {
|
||||
return mastodonApi.homeTimeline(maxId, sinceIdMinusOne, limit + 1)
|
||||
.map { response ->
|
||||
|
@ -87,8 +103,11 @@ class TimelineRepositoryImpl(
|
|||
}
|
||||
|
||||
private fun addFromDbIfNeeded(
|
||||
accountId: Long, statuses: List<Either<Placeholder, Status>>,
|
||||
maxId: String?, sinceId: String?, limit: Int,
|
||||
accountId: Long,
|
||||
statuses: List<Either<Placeholder, Status>>,
|
||||
maxId: String?,
|
||||
sinceId: String?,
|
||||
limit: Int,
|
||||
requestMode: TimelineRequestMode
|
||||
): Single<List<TimelineStatus>> {
|
||||
return if (requestMode != NETWORK && statuses.size < 2) {
|
||||
|
@ -113,7 +132,9 @@ class TimelineRepositoryImpl(
|
|||
}
|
||||
|
||||
private fun getStatusesFromDb(
|
||||
accountId: Long, maxId: String?, sinceId: String?,
|
||||
accountId: Long,
|
||||
maxId: String?,
|
||||
sinceId: String?,
|
||||
limit: Int
|
||||
): Single<out List<TimelineStatus>> {
|
||||
return timelineDao.getStatusesForAccount(accountId, maxId, sinceId, limit)
|
||||
|
@ -124,8 +145,10 @@ class TimelineRepositoryImpl(
|
|||
}
|
||||
|
||||
private fun saveStatusesToDb(
|
||||
accountId: Long, statuses: List<Status>,
|
||||
maxId: String?, sinceId: String?
|
||||
accountId: Long,
|
||||
statuses: List<Status>,
|
||||
maxId: String?,
|
||||
sinceId: String?
|
||||
): List<Either<Placeholder, Status>> {
|
||||
var placeholderToInsert: Placeholder? = null
|
||||
|
||||
|
@ -347,7 +370,6 @@ fun TimelineAccountEntity.toAccount(gson: Gson): Account {
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
fun Placeholder.toEntity(timelineUserId: Long): TimelineStatusEntity {
|
||||
return TimelineStatusEntity(
|
||||
serverId = this.id,
|
||||
|
|
|
@ -3,7 +3,20 @@ package com.keylesspalace.tusky.components.timeline
|
|||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.keylesspalace.tusky.appstore.*
|
||||
import com.keylesspalace.tusky.appstore.BlockEvent
|
||||
import com.keylesspalace.tusky.appstore.BookmarkEvent
|
||||
import com.keylesspalace.tusky.appstore.DomainMuteEvent
|
||||
import com.keylesspalace.tusky.appstore.Event
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.FavoriteEvent
|
||||
import com.keylesspalace.tusky.appstore.MuteConversationEvent
|
||||
import com.keylesspalace.tusky.appstore.MuteEvent
|
||||
import com.keylesspalace.tusky.appstore.PinEvent
|
||||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||
import com.keylesspalace.tusky.appstore.ReblogEvent
|
||||
import com.keylesspalace.tusky.appstore.StatusComposedEvent
|
||||
import com.keylesspalace.tusky.appstore.StatusDeletedEvent
|
||||
import com.keylesspalace.tusky.appstore.UnfollowEvent
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.entity.Poll
|
||||
|
@ -12,7 +25,15 @@ import com.keylesspalace.tusky.network.FilterModel
|
|||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.network.TimelineCases
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.Either
|
||||
import com.keylesspalace.tusky.util.HttpHeaderLink
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.util.RxAwareViewModel
|
||||
import com.keylesspalace.tusky.util.dec
|
||||
import com.keylesspalace.tusky.util.firstIsInstanceOrNull
|
||||
import com.keylesspalace.tusky.util.inc
|
||||
import com.keylesspalace.tusky.util.isLessThan
|
||||
import com.keylesspalace.tusky.util.toViewData
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
|
@ -238,8 +259,8 @@ class TimelineViewModel @Inject constructor(
|
|||
private fun addStatusesBelow(statuses: MutableList<Either<Placeholder, Status>>) {
|
||||
val fullFetch = isFullFetch(statuses)
|
||||
// Remove placeholder in the bottom if it's there
|
||||
if (this.statuses.isNotEmpty()
|
||||
&& this.statuses.last() !is StatusViewData.Concrete
|
||||
if (this.statuses.isNotEmpty() &&
|
||||
this.statuses.last() !is StatusViewData.Concrete
|
||||
) {
|
||||
this.statuses.removeAt(this.statuses.lastIndex)
|
||||
}
|
||||
|
@ -264,7 +285,7 @@ class TimelineViewModel @Inject constructor(
|
|||
|
||||
fun loadGap(position: Int): Job {
|
||||
return viewModelScope.launch {
|
||||
//check bounds before accessing list,
|
||||
// check bounds before accessing list,
|
||||
if (statuses.size < position || position <= 0) {
|
||||
Log.e(TAG, "Wrong gap position: $position")
|
||||
return@launch
|
||||
|
@ -318,7 +339,6 @@ class TimelineViewModel @Inject constructor(
|
|||
} catch (t: Exception) {
|
||||
ifExpected(t) {
|
||||
Log.d(TAG, "Failed to reblog status " + status.id, t)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -485,9 +505,9 @@ class TimelineViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun shouldFilterStatus(status: Status): Boolean {
|
||||
return status.inReplyToId != null && filterRemoveReplies
|
||||
|| status.reblog != null && filterRemoveReblogs
|
||||
|| filterModel.shouldFilterStatus(status.actionableStatus)
|
||||
return status.inReplyToId != null && filterRemoveReplies ||
|
||||
status.reblog != null && filterRemoveReblogs ||
|
||||
filterModel.shouldFilterStatus(status.actionableStatus)
|
||||
}
|
||||
|
||||
private fun extractNextId(response: Response<*>): String? {
|
||||
|
@ -644,7 +664,8 @@ class TimelineViewModel @Inject constructor(
|
|||
|
||||
private fun replacePlaceholderWithStatuses(
|
||||
newStatuses: MutableList<Either<Placeholder, Status>>,
|
||||
fullFetch: Boolean, pos: Int
|
||||
fullFetch: Boolean,
|
||||
pos: Int
|
||||
) {
|
||||
val placeholder = statuses[pos]
|
||||
if (placeholder is StatusViewData.Placeholder) {
|
||||
|
@ -873,9 +894,11 @@ class TimelineViewModel @Inject constructor(
|
|||
Log.e(TAG, "Failed to fetch filters", t)
|
||||
return@launch
|
||||
}
|
||||
filterModel.initWithFilters(filters.filter {
|
||||
filterContextMatchesKind(kind, it.context)
|
||||
})
|
||||
filterModel.initWithFilters(
|
||||
filters.filter {
|
||||
filterContextMatchesKind(kind, it.context)
|
||||
}
|
||||
)
|
||||
filterViewData(this@TimelineViewModel.statuses)
|
||||
}
|
||||
}
|
||||
|
@ -891,7 +914,6 @@ class TimelineViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
private const val TAG = "TimelineVM"
|
||||
internal const val LOAD_AT_ONCE = 30
|
||||
|
@ -900,4 +922,4 @@ class TimelineViewModel @Inject constructor(
|
|||
enum class Kind {
|
||||
HOME, PUBLIC_LOCAL, PUBLIC_FEDERATED, TAG, USER, USER_PINNED, USER_WITH_REPLIES, FAVOURITES, LIST, BOOKMARKS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,11 @@
|
|||
|
||||
package com.keylesspalace.tusky.db
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
|
||||
@Dao
|
||||
interface AccountDao {
|
||||
|
@ -27,5 +31,4 @@ interface AccountDao {
|
|||
|
||||
@Query("SELECT * FROM AccountEntity ORDER BY id ASC")
|
||||
fun loadAll(): List<AccountEntity>
|
||||
|
||||
}
|
||||
|
|
|
@ -21,42 +21,49 @@ import androidx.room.PrimaryKey
|
|||
import androidx.room.TypeConverters
|
||||
import com.keylesspalace.tusky.TabData
|
||||
import com.keylesspalace.tusky.defaultTabs
|
||||
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
|
||||
@Entity(indices = [Index(value = ["domain", "accountId"],
|
||||
unique = true)])
|
||||
@Entity(
|
||||
indices = [
|
||||
Index(
|
||||
value = ["domain", "accountId"],
|
||||
unique = true
|
||||
)
|
||||
]
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
data class AccountEntity(@field:PrimaryKey(autoGenerate = true) var id: Long,
|
||||
val domain: String,
|
||||
var accessToken: String,
|
||||
var isActive: Boolean,
|
||||
var accountId: String = "",
|
||||
var username: String = "",
|
||||
var displayName: String = "",
|
||||
var profilePictureUrl: String = "",
|
||||
var notificationsEnabled: Boolean = true,
|
||||
var notificationsMentioned: Boolean = true,
|
||||
var notificationsFollowed: Boolean = true,
|
||||
var notificationsFollowRequested: Boolean = false,
|
||||
var notificationsReblogged: Boolean = true,
|
||||
var notificationsFavorited: Boolean = true,
|
||||
var notificationsPolls: Boolean = true,
|
||||
var notificationsSubscriptions: Boolean = true,
|
||||
var notificationSound: Boolean = true,
|
||||
var notificationVibration: Boolean = true,
|
||||
var notificationLight: Boolean = true,
|
||||
var defaultPostPrivacy: Status.Visibility = Status.Visibility.PUBLIC,
|
||||
var defaultMediaSensitivity: Boolean = false,
|
||||
var alwaysShowSensitiveMedia: Boolean = false,
|
||||
var alwaysOpenSpoiler: Boolean = false,
|
||||
var mediaPreviewEnabled: Boolean = true,
|
||||
var lastNotificationId: String = "0",
|
||||
var activeNotifications: String = "[]",
|
||||
var emojis: List<Emoji> = emptyList(),
|
||||
var tabPreferences: List<TabData> = defaultTabs(),
|
||||
var notificationsFilter: String = "[\"follow_request\"]") {
|
||||
data class AccountEntity(
|
||||
@field:PrimaryKey(autoGenerate = true) var id: Long,
|
||||
val domain: String,
|
||||
var accessToken: String,
|
||||
var isActive: Boolean,
|
||||
var accountId: String = "",
|
||||
var username: String = "",
|
||||
var displayName: String = "",
|
||||
var profilePictureUrl: String = "",
|
||||
var notificationsEnabled: Boolean = true,
|
||||
var notificationsMentioned: Boolean = true,
|
||||
var notificationsFollowed: Boolean = true,
|
||||
var notificationsFollowRequested: Boolean = false,
|
||||
var notificationsReblogged: Boolean = true,
|
||||
var notificationsFavorited: Boolean = true,
|
||||
var notificationsPolls: Boolean = true,
|
||||
var notificationsSubscriptions: Boolean = true,
|
||||
var notificationSound: Boolean = true,
|
||||
var notificationVibration: Boolean = true,
|
||||
var notificationLight: Boolean = true,
|
||||
var defaultPostPrivacy: Status.Visibility = Status.Visibility.PUBLIC,
|
||||
var defaultMediaSensitivity: Boolean = false,
|
||||
var alwaysShowSensitiveMedia: Boolean = false,
|
||||
var alwaysOpenSpoiler: Boolean = false,
|
||||
var mediaPreviewEnabled: Boolean = true,
|
||||
var lastNotificationId: String = "0",
|
||||
var activeNotifications: String = "[]",
|
||||
var emojis: List<Emoji> = emptyList(),
|
||||
var tabPreferences: List<TabData> = defaultTabs(),
|
||||
var notificationsFilter: String = "[\"follow_request\"]"
|
||||
) {
|
||||
|
||||
val identifier: String
|
||||
get() = "$domain:$accountId"
|
||||
|
|
|
@ -18,7 +18,7 @@ package com.keylesspalace.tusky.db
|
|||
import android.util.Log
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
@ -66,7 +66,6 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
|||
val maxAccountId = accounts.maxByOrNull { it.id }?.id ?: 0
|
||||
val newAccountId = maxAccountId + 1
|
||||
activeAccount = AccountEntity(id = newAccountId, domain = domain.lowercase(Locale.ROOT), accessToken = accessToken, isActive = true)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,7 +78,6 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
|||
Log.d(TAG, "saveAccount: saving account with id " + account.id)
|
||||
accountDao.insertOrReplace(account)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,9 +101,7 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
|||
activeAccount = null
|
||||
}
|
||||
return activeAccount
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -129,13 +125,12 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
|||
val accountIndex = accounts.indexOf(it)
|
||||
|
||||
if (accountIndex != -1) {
|
||||
//in case the user was already logged in with this account, remove the old information
|
||||
// in case the user was already logged in with this account, remove the old information
|
||||
accounts.removeAt(accountIndex)
|
||||
accounts.add(accountIndex, it)
|
||||
} else {
|
||||
accounts.add(it)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,5 +189,4 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
|||
id == accountId
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue