Merge branch 'develop'
This commit is contained in:
commit
7d11761012
233 changed files with 5565 additions and 3812 deletions
|
@ -1,7 +1,7 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
|
||||
apply from: "../instance-build.gradle"
|
||||
|
||||
|
@ -20,8 +20,8 @@ android {
|
|||
applicationId APP_ID
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 80
|
||||
versionName "14.0"
|
||||
versionCode 81
|
||||
versionName "15.0 beta 1"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
|
@ -64,9 +64,6 @@ android {
|
|||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
androidExtensions {
|
||||
experimental = true
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
@ -100,7 +97,7 @@ project.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
|||
}
|
||||
|
||||
ext.lifecycleVersion = "2.2.0"
|
||||
ext.roomVersion = '2.2.5'
|
||||
ext.roomVersion = '2.3.0'
|
||||
ext.retrofitVersion = '2.9.0'
|
||||
ext.okhttpVersion = '4.9.0'
|
||||
ext.glideVersion = '4.11.0'
|
||||
|
@ -116,7 +113,7 @@ dependencies {
|
|||
implementation "androidx.fragment:fragment-ktx:1.2.5"
|
||||
implementation "androidx.browser:browser:1.3.0"
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.2.0"
|
||||
implementation "androidx.exifinterface:exifinterface:1.3.2"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
||||
|
@ -125,6 +122,7 @@ dependencies {
|
|||
implementation "androidx.emoji:emoji-appcompat:1.1.0"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycleVersion"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
||||
implementation "androidx.paging:paging-runtime-ktx:2.1.2"
|
||||
|
@ -134,7 +132,7 @@ dependencies {
|
|||
implementation "androidx.room:room-rxjava2:$roomVersion"
|
||||
kapt "androidx.room:room-compiler:$roomVersion"
|
||||
|
||||
implementation "com.google.android.material:material:1.2.1"
|
||||
implementation "com.google.android.material:material:1.3.0"
|
||||
|
||||
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
|
||||
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
|
||||
|
@ -142,7 +140,6 @@ dependencies {
|
|||
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:$okhttpVersion"
|
||||
implementation "com.squareup.okhttp3:okhttp-tls:$okhttpVersion"
|
||||
|
||||
implementation "org.conscrypt:conscrypt-android:2.5.1"
|
||||
|
||||
|
|
|
@ -9,19 +9,20 @@ import android.text.method.LinkMovementMethod
|
|||
import android.text.style.URLSpan
|
||||
import android.text.util.Linkify
|
||||
import android.widget.TextView
|
||||
import com.keylesspalace.tusky.databinding.ActivityAboutBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.util.CustomURLSpan
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import kotlinx.android.synthetic.main.activity_about.*
|
||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||
|
||||
class AboutActivity : BottomSheetActivity(), Injectable {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_about)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
val binding = ActivityAboutBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
supportActionBar?.run {
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowHomeEnabled(true)
|
||||
|
@ -29,26 +30,24 @@ class AboutActivity : BottomSheetActivity(), Injectable {
|
|||
|
||||
setTitle(R.string.about_title_activity)
|
||||
|
||||
versionTextView.text = getString(R.string.about_app_version, getString(R.string.app_name), BuildConfig.VERSION_NAME)
|
||||
binding.versionTextView.text = getString(R.string.about_app_version, getString(R.string.app_name), BuildConfig.VERSION_NAME)
|
||||
|
||||
if(BuildConfig.CUSTOM_INSTANCE.isBlank()) {
|
||||
aboutPoweredByTusky.hide()
|
||||
binding.aboutPoweredByTusky.hide()
|
||||
}
|
||||
|
||||
aboutLicenseInfoTextView.setClickableTextWithoutUnderlines(R.string.about_tusky_license)
|
||||
aboutWebsiteInfoTextView.setClickableTextWithoutUnderlines(R.string.about_project_site)
|
||||
aboutBugsFeaturesInfoTextView.setClickableTextWithoutUnderlines(R.string.about_bug_feature_request_site)
|
||||
binding.aboutLicenseInfoTextView.setClickableTextWithoutUnderlines(R.string.about_tusky_license)
|
||||
binding.aboutWebsiteInfoTextView.setClickableTextWithoutUnderlines(R.string.about_project_site)
|
||||
binding.aboutBugsFeaturesInfoTextView.setClickableTextWithoutUnderlines(R.string.about_bug_feature_request_site)
|
||||
|
||||
tuskyProfileButton.setOnClickListener {
|
||||
binding.tuskyProfileButton.setOnClickListener {
|
||||
viewUrl(BuildConfig.SUPPORT_ACCOUNT_URL)
|
||||
}
|
||||
|
||||
aboutLicensesButton.setOnClickListener {
|
||||
binding.aboutLicensesButton.setOnClickListener {
|
||||
startActivityWithSlideInAnimation(Intent(this, LicenseActivity::class.java))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun TextView.setClickableTextWithoutUnderlines(@StringRes textId: Int) {
|
||||
|
@ -73,5 +72,4 @@ private fun TextView.setClickableTextWithoutUnderlines(@StringRes textId: Int) {
|
|||
setText(builder)
|
||||
linksClickable = true
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ import com.google.android.material.tabs.TabLayoutMediator
|
|||
import com.keylesspalace.tusky.adapter.AccountFieldAdapter
|
||||
import com.keylesspalace.tusky.components.compose.ComposeActivity
|
||||
import com.keylesspalace.tusky.components.report.ReportActivity
|
||||
import com.keylesspalace.tusky.databinding.ActivityAccountBinding
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.entity.Relationship
|
||||
|
@ -63,8 +64,6 @@ import com.keylesspalace.tusky.view.showMuteAccountDialog
|
|||
import com.keylesspalace.tusky.viewmodel.AccountViewModel
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import kotlinx.android.synthetic.main.activity_account.*
|
||||
import kotlinx.android.synthetic.main.view_account_moved.*
|
||||
import java.text.NumberFormat
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.abs
|
||||
|
@ -78,6 +77,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
private val viewModel: AccountViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private val binding: ActivityAccountBinding by viewBinding(ActivityAccountBinding::inflate)
|
||||
|
||||
private lateinit var accountFieldAdapter : AccountFieldAdapter
|
||||
|
||||
private var followState: FollowState = FollowState.NOT_FOLLOWING
|
||||
|
@ -118,7 +119,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
super.onCreate(savedInstanceState)
|
||||
loadResources()
|
||||
makeNotificationBarTransparent()
|
||||
setContentView(R.layout.activity_account)
|
||||
setContentView(binding.root)
|
||||
|
||||
// Obtain information to fill out the profile.
|
||||
viewModel.setAccountInfo(intent.getStringExtra(KEY_ACCOUNT_ID)!!)
|
||||
|
@ -136,9 +137,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
if (viewModel.isSelf) {
|
||||
updateButtons()
|
||||
saveNoteInfo.hide()
|
||||
binding.saveNoteInfo.hide()
|
||||
} else {
|
||||
saveNoteInfo.visibility = View.INVISIBLE
|
||||
binding.saveNoteInfo.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,16 +159,16 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
*/
|
||||
private fun setupAccountViews() {
|
||||
// Initialise the default UI states.
|
||||
accountFloatingActionButton.hide()
|
||||
accountFollowButton.hide()
|
||||
accountMuteButton.hide()
|
||||
accountFollowsYouTextView.hide()
|
||||
binding.accountFloatingActionButton.hide()
|
||||
binding.accountFollowButton.hide()
|
||||
binding.accountMuteButton.hide()
|
||||
binding.accountFollowsYouTextView.hide()
|
||||
|
||||
// setup the RecyclerView for the account fields
|
||||
accountFieldAdapter = AccountFieldAdapter(this, animateEmojis)
|
||||
accountFieldList.isNestedScrollingEnabled = false
|
||||
accountFieldList.layoutManager = LinearLayoutManager(this)
|
||||
accountFieldList.adapter = accountFieldAdapter
|
||||
binding.accountFieldList.isNestedScrollingEnabled = false
|
||||
binding.accountFieldList.layoutManager = LinearLayoutManager(this)
|
||||
binding.accountFieldList.adapter = accountFieldAdapter
|
||||
|
||||
|
||||
val accountListClickListener = { v: View ->
|
||||
|
@ -179,15 +180,15 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
val accountListIntent = AccountListActivity.newIntent(this, type, viewModel.accountId)
|
||||
startActivityWithSlideInAnimation(accountListIntent)
|
||||
}
|
||||
accountFollowers.setOnClickListener(accountListClickListener)
|
||||
accountFollowing.setOnClickListener(accountListClickListener)
|
||||
binding.accountFollowers.setOnClickListener(accountListClickListener)
|
||||
binding.accountFollowing.setOnClickListener(accountListClickListener)
|
||||
|
||||
accountStatuses.setOnClickListener {
|
||||
binding.accountStatuses.setOnClickListener {
|
||||
// Make nice ripple effect on tab
|
||||
accountTabLayout.getTabAt(0)!!.select()
|
||||
val poorTabView = (accountTabLayout.getChildAt(0) as ViewGroup).getChildAt(0)
|
||||
binding.accountTabLayout.getTabAt(0)!!.select()
|
||||
val poorTabView = (binding.accountTabLayout.getChildAt(0) as ViewGroup).getChildAt(0)
|
||||
poorTabView.isPressed = true
|
||||
accountTabLayout.postDelayed({ poorTabView.isPressed = false }, 300)
|
||||
binding.accountTabLayout.postDelayed({ poorTabView.isPressed = false }, 300)
|
||||
}
|
||||
|
||||
// If wellbeing mode is enabled, follow stats and posts count should be hidden
|
||||
|
@ -195,11 +196,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_PROFILE, false)
|
||||
|
||||
if (wellbeingEnabled) {
|
||||
accountStatuses.hide()
|
||||
accountFollowers.hide()
|
||||
accountFollowing.hide()
|
||||
binding.accountStatuses.hide()
|
||||
binding.accountFollowers.hide()
|
||||
binding.accountFollowing.hide()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -209,19 +209,19 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
// Setup the tabs and timeline pager.
|
||||
adapter = AccountPagerAdapter(this, viewModel.accountId)
|
||||
|
||||
accountFragmentViewPager.adapter = adapter
|
||||
accountFragmentViewPager.offscreenPageLimit = 2
|
||||
binding.accountFragmentViewPager.adapter = adapter
|
||||
binding.accountFragmentViewPager.offscreenPageLimit = 2
|
||||
|
||||
val pageTitles = arrayOf(getString(R.string.title_statuses), getString(R.string.title_statuses_with_replies), getString(R.string.title_statuses_pinned), getString(R.string.title_media))
|
||||
|
||||
TabLayoutMediator(accountTabLayout, accountFragmentViewPager) { tab, position ->
|
||||
TabLayoutMediator(binding.accountTabLayout, binding.accountFragmentViewPager) { tab, position ->
|
||||
tab.text = pageTitles[position]
|
||||
}.attach()
|
||||
|
||||
val pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin)
|
||||
accountFragmentViewPager.setPageTransformer(MarginPageTransformer(pageMargin))
|
||||
binding.accountFragmentViewPager.setPageTransformer(MarginPageTransformer(pageMargin))
|
||||
|
||||
accountTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
binding.accountTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabReselected(tab: TabLayout.Tab?) {
|
||||
tab?.position?.let { position ->
|
||||
(adapter.getFragment(position) as? ReselectableFragment)?.onReselect()
|
||||
|
@ -237,17 +237,17 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
private fun setupToolbar() {
|
||||
// set toolbar top margin according to system window insets
|
||||
accountCoordinatorLayout.setOnApplyWindowInsetsListener { _, insets ->
|
||||
binding.accountCoordinatorLayout.setOnApplyWindowInsetsListener { _, insets ->
|
||||
val top = insets.systemWindowInsetTop
|
||||
|
||||
val toolbarParams = accountToolbar.layoutParams as CollapsingToolbarLayout.LayoutParams
|
||||
val toolbarParams = binding.accountToolbar.layoutParams as CollapsingToolbarLayout.LayoutParams
|
||||
toolbarParams.topMargin = top
|
||||
|
||||
insets.consumeSystemWindowInsets()
|
||||
}
|
||||
|
||||
// Setup the toolbar.
|
||||
setSupportActionBar(accountToolbar)
|
||||
setSupportActionBar(binding.accountToolbar)
|
||||
supportActionBar?.run {
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowHomeEnabled(true)
|
||||
|
@ -258,9 +258,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
val toolbarBackground = MaterialShapeDrawable.createWithElevationOverlay(this, appBarElevation)
|
||||
toolbarBackground.fillColor = ColorStateList.valueOf(Color.TRANSPARENT)
|
||||
accountToolbar.background = toolbarBackground
|
||||
binding.accountToolbar.background = toolbarBackground
|
||||
|
||||
accountHeaderInfoContainer.background = MaterialShapeDrawable.createWithElevationOverlay(this, appBarElevation)
|
||||
binding.accountHeaderInfoContainer.background = MaterialShapeDrawable.createWithElevationOverlay(this, appBarElevation)
|
||||
|
||||
val avatarBackground = MaterialShapeDrawable.createWithElevationOverlay(this, appBarElevation).apply {
|
||||
fillColor = ColorStateList.valueOf(toolbarColor)
|
||||
|
@ -269,10 +269,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
.setAllCornerSizes(resources.getDimension(R.dimen.account_avatar_background_radius))
|
||||
.build()
|
||||
}
|
||||
accountAvatarImageView.background = avatarBackground
|
||||
binding.accountAvatarImageView.background = avatarBackground
|
||||
|
||||
// Add a listener to change the toolbar icon color when it enters/exits its collapsed state.
|
||||
accountAppBarLayout.addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener {
|
||||
binding.accountAppBarLayout.addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener {
|
||||
|
||||
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
|
||||
|
||||
|
@ -289,19 +289,19 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
if (hideFab && !viewModel.isSelf && !blocking) {
|
||||
if (verticalOffset > oldOffset) {
|
||||
accountFloatingActionButton.show()
|
||||
binding.accountFloatingActionButton.show()
|
||||
}
|
||||
if (verticalOffset < oldOffset) {
|
||||
accountFloatingActionButton.hide()
|
||||
binding.accountFloatingActionButton.hide()
|
||||
}
|
||||
}
|
||||
|
||||
val scaledAvatarSize = (avatarSize + verticalOffset) / avatarSize
|
||||
|
||||
accountAvatarImageView.scaleX = scaledAvatarSize
|
||||
accountAvatarImageView.scaleY = scaledAvatarSize
|
||||
binding.accountAvatarImageView.scaleX = scaledAvatarSize
|
||||
binding.accountAvatarImageView.scaleY = scaledAvatarSize
|
||||
|
||||
accountAvatarImageView.visible(scaledAvatarSize > 0)
|
||||
binding.accountAvatarImageView.visible(scaledAvatarSize > 0)
|
||||
|
||||
val transparencyPercent = (abs(verticalOffset) / titleVisibleHeight.toFloat()).coerceAtMost(1f)
|
||||
|
||||
|
@ -311,7 +311,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
toolbarBackground.fillColor = ColorStateList.valueOf(evaluatedToolbarColor)
|
||||
|
||||
swipeToRefreshLayout.isEnabled = verticalOffset == 0
|
||||
binding.swipeToRefreshLayout.isEnabled = verticalOffset == 0
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -331,7 +331,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
when (it) {
|
||||
is Success -> onAccountChanged(it.data)
|
||||
is Error -> {
|
||||
Snackbar.make(accountCoordinatorLayout, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
Snackbar.make(binding.accountCoordinatorLayout, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry) { viewModel.refresh() }
|
||||
.show()
|
||||
}
|
||||
|
@ -344,7 +344,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
}
|
||||
|
||||
if (it is Error) {
|
||||
Snackbar.make(accountCoordinatorLayout, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
Snackbar.make(binding.accountCoordinatorLayout, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry) { viewModel.refresh() }
|
||||
.show()
|
||||
}
|
||||
|
@ -355,7 +355,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
accountFieldAdapter.notifyDataSetChanged()
|
||||
})
|
||||
viewModel.noteSaved.observe(this) {
|
||||
saveNoteInfo.visible(it, View.INVISIBLE)
|
||||
binding.saveNoteInfo.visible(it, View.INVISIBLE)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -363,32 +363,32 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
* Setup swipe to refresh layout
|
||||
*/
|
||||
private fun setupRefreshLayout() {
|
||||
swipeToRefreshLayout.setOnRefreshListener {
|
||||
binding.swipeToRefreshLayout.setOnRefreshListener {
|
||||
viewModel.refresh()
|
||||
adapter.refreshContent()
|
||||
}
|
||||
viewModel.isRefreshing.observe(this, { isRefreshing ->
|
||||
swipeToRefreshLayout.isRefreshing = isRefreshing == true
|
||||
binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true
|
||||
})
|
||||
swipeToRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
binding.swipeToRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
}
|
||||
|
||||
private fun onAccountChanged(account: Account?) {
|
||||
loadedAccount = account ?: return
|
||||
|
||||
val usernameFormatted = getString(R.string.status_username_format, account.username)
|
||||
accountUsernameTextView.text = usernameFormatted
|
||||
accountDisplayNameTextView.text = account.name.emojify(account.emojis, accountDisplayNameTextView, animateEmojis)
|
||||
binding.accountUsernameTextView.text = usernameFormatted
|
||||
binding.accountDisplayNameTextView.text = account.name.emojify(account.emojis, binding.accountDisplayNameTextView, animateEmojis)
|
||||
|
||||
val emojifiedNote = account.note.emojify(account.emojis, accountNoteTextView, animateEmojis)
|
||||
LinkHelper.setClickableText(accountNoteTextView, emojifiedNote, null, this)
|
||||
val emojifiedNote = account.note.emojify(account.emojis, binding.accountNoteTextView, animateEmojis)
|
||||
LinkHelper.setClickableText(binding.accountNoteTextView, emojifiedNote, null, this)
|
||||
|
||||
// accountFieldAdapter.fields = account.fields ?: emptyList()
|
||||
accountFieldAdapter.emojis = account.emojis ?: emptyList()
|
||||
accountFieldAdapter.notifyDataSetChanged()
|
||||
|
||||
accountLockedImageView.visible(account.locked)
|
||||
accountBadgeTextView.visible(account.bot)
|
||||
binding.accountLockedImageView.visible(account.locked)
|
||||
binding.accountBadgeTextView.visible(account.bot)
|
||||
|
||||
updateAccountAvatar()
|
||||
updateToolbar()
|
||||
|
@ -397,7 +397,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
updateAccountStats()
|
||||
invalidateOptionsMenu()
|
||||
|
||||
accountMuteButton.setOnClickListener {
|
||||
binding.accountMuteButton.setOnClickListener {
|
||||
viewModel.unmuteAccount()
|
||||
updateMuteButton()
|
||||
}
|
||||
|
@ -411,7 +411,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
loadAvatar(
|
||||
account.avatar,
|
||||
accountAvatarImageView,
|
||||
binding.accountAvatarImageView,
|
||||
resources.getDimensionPixelSize(R.dimen.avatar_radius_94dp),
|
||||
animateAvatar
|
||||
)
|
||||
|
@ -420,10 +420,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
.asBitmap()
|
||||
.load(account.header)
|
||||
.centerCrop()
|
||||
.into(accountHeaderImageView)
|
||||
.into(binding.accountHeaderImageView)
|
||||
|
||||
|
||||
accountAvatarImageView.setOnClickListener { avatarView ->
|
||||
binding.accountAvatarImageView.setOnClickListener { avatarView ->
|
||||
val intent = ViewMediaActivity.newSingleImageIntent(avatarView.context, account.avatar)
|
||||
|
||||
avatarView.transitionName = account.avatar
|
||||
|
@ -440,7 +440,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
private fun updateToolbar() {
|
||||
loadedAccount?.let { account ->
|
||||
|
||||
val emojifiedName = account.name.emojify(account.emojis, accountToolbar, animateEmojis)
|
||||
val emojifiedName = account.name.emojify(account.emojis, binding.accountToolbar, animateEmojis)
|
||||
|
||||
try {
|
||||
supportActionBar?.title = EmojiCompat.get().process(emojifiedName)
|
||||
|
@ -457,28 +457,27 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
private fun updateMovedAccount() {
|
||||
loadedAccount?.moved?.let { movedAccount ->
|
||||
|
||||
accountMovedView?.show()
|
||||
binding.accountMovedView.show()
|
||||
|
||||
// necessary because accountMovedView is now replaced in layout hierachy
|
||||
findViewById<View>(R.id.accountMovedViewLayout).setOnClickListener {
|
||||
binding.accountMovedView.setOnClickListener {
|
||||
onViewAccount(movedAccount.id)
|
||||
}
|
||||
|
||||
accountMovedDisplayName.text = movedAccount.name
|
||||
accountMovedUsername.text = getString(R.string.status_username_format, movedAccount.username)
|
||||
binding.accountMovedDisplayName.text = movedAccount.name
|
||||
binding.accountMovedUsername.text = getString(R.string.status_username_format, movedAccount.username)
|
||||
|
||||
val avatarRadius = resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp)
|
||||
|
||||
loadAvatar(movedAccount.avatar, accountMovedAvatar, avatarRadius, animateAvatar)
|
||||
loadAvatar(movedAccount.avatar, binding.accountMovedAvatar, avatarRadius, animateAvatar)
|
||||
|
||||
accountMovedText.text = getString(R.string.account_moved_description, movedAccount.name)
|
||||
binding.accountMovedText.text = getString(R.string.account_moved_description, movedAccount.name)
|
||||
|
||||
// this is necessary because API 19 can't handle vector compound drawables
|
||||
val movedIcon = ContextCompat.getDrawable(this, R.drawable.ic_briefcase)?.mutate()
|
||||
val textColor = ThemeUtils.getColor(this, android.R.attr.textColorTertiary)
|
||||
movedIcon?.colorFilter = PorterDuffColorFilter(textColor, PorterDuff.Mode.SRC_IN)
|
||||
|
||||
accountMovedText.setCompoundDrawablesRelativeWithIntrinsicBounds(movedIcon, null, null, null)
|
||||
binding.accountMovedText.setCompoundDrawablesRelativeWithIntrinsicBounds(movedIcon, null, null, null)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -489,8 +488,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
private fun updateRemoteAccount() {
|
||||
loadedAccount?.let { account ->
|
||||
if (account.isRemote()) {
|
||||
accountRemoveView.show()
|
||||
accountRemoveView.setOnClickListener {
|
||||
binding.accountRemoveView.show()
|
||||
binding.accountRemoveView.setOnClickListener {
|
||||
LinkHelper.openLink(account.url, this)
|
||||
}
|
||||
}
|
||||
|
@ -503,13 +502,13 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
private fun updateAccountStats() {
|
||||
loadedAccount?.let { account ->
|
||||
val numberFormat = NumberFormat.getNumberInstance()
|
||||
accountFollowersTextView.text = numberFormat.format(account.followersCount)
|
||||
accountFollowingTextView.text = numberFormat.format(account.followingCount)
|
||||
accountStatusesTextView.text = numberFormat.format(account.statusesCount)
|
||||
binding.accountFollowersTextView.text = numberFormat.format(account.followersCount)
|
||||
binding.accountFollowingTextView.text = numberFormat.format(account.followingCount)
|
||||
binding.accountStatusesTextView.text = numberFormat.format(account.statusesCount)
|
||||
|
||||
accountFloatingActionButton.setOnClickListener { mention() }
|
||||
binding.accountFloatingActionButton.setOnClickListener { mention() }
|
||||
|
||||
accountFollowButton.setOnClickListener {
|
||||
binding.accountFollowButton.setOnClickListener {
|
||||
if (viewModel.isSelf) {
|
||||
val intent = Intent(this@AccountActivity, EditProfileActivity::class.java)
|
||||
startActivity(intent)
|
||||
|
@ -552,14 +551,14 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_PROFILE, false)
|
||||
|
||||
accountFollowsYouTextView.visible(relation.followedBy && !wellbeingEnabled)
|
||||
binding.accountFollowsYouTextView.visible(relation.followedBy && !wellbeingEnabled)
|
||||
|
||||
// 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)) {
|
||||
accountSubscribeButton.show()
|
||||
accountSubscribeButton.setOnClickListener {
|
||||
binding.accountSubscribeButton.show()
|
||||
binding.accountSubscribeButton.setOnClickListener {
|
||||
viewModel.changeSubscribingState()
|
||||
}
|
||||
if(relation.notifying != null)
|
||||
|
@ -569,12 +568,12 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
}
|
||||
|
||||
// remove the listener so it doesn't fire on non-user changes
|
||||
accountNoteTextInputLayout.editText?.removeTextChangedListener(noteWatcher)
|
||||
binding.accountNoteTextInputLayout.editText?.removeTextChangedListener(noteWatcher)
|
||||
|
||||
accountNoteTextInputLayout.visible(relation.note != null)
|
||||
accountNoteTextInputLayout.editText?.setText(relation.note)
|
||||
binding.accountNoteTextInputLayout.visible(relation.note != null)
|
||||
binding.accountNoteTextInputLayout.editText?.setText(relation.note)
|
||||
|
||||
accountNoteTextInputLayout.editText?.addTextChangedListener(noteWatcher)
|
||||
binding.accountNoteTextInputLayout.editText?.addTextChangedListener(noteWatcher)
|
||||
|
||||
updateButtons()
|
||||
}
|
||||
|
@ -587,22 +586,22 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
private fun updateFollowButton() {
|
||||
if (viewModel.isSelf) {
|
||||
accountFollowButton.setText(R.string.action_edit_own_profile)
|
||||
binding.accountFollowButton.setText(R.string.action_edit_own_profile)
|
||||
return
|
||||
}
|
||||
if (blocking) {
|
||||
accountFollowButton.setText(R.string.action_unblock)
|
||||
binding.accountFollowButton.setText(R.string.action_unblock)
|
||||
return
|
||||
}
|
||||
when (followState) {
|
||||
FollowState.NOT_FOLLOWING -> {
|
||||
accountFollowButton.setText(R.string.action_follow)
|
||||
binding.accountFollowButton.setText(R.string.action_follow)
|
||||
}
|
||||
FollowState.REQUESTED -> {
|
||||
accountFollowButton.setText(R.string.state_follow_requested)
|
||||
binding.accountFollowButton.setText(R.string.state_follow_requested)
|
||||
}
|
||||
FollowState.FOLLOWING -> {
|
||||
accountFollowButton.setText(R.string.action_unfollow)
|
||||
binding.accountFollowButton.setText(R.string.action_unfollow)
|
||||
}
|
||||
}
|
||||
updateSubscribeButton()
|
||||
|
@ -610,23 +609,23 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
private fun updateMuteButton() {
|
||||
if (muting) {
|
||||
accountMuteButton.setIconResource(R.drawable.ic_unmute_24dp)
|
||||
binding.accountMuteButton.setIconResource(R.drawable.ic_unmute_24dp)
|
||||
} else {
|
||||
accountMuteButton.hide()
|
||||
binding.accountMuteButton.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSubscribeButton() {
|
||||
if(followState != FollowState.FOLLOWING) {
|
||||
accountSubscribeButton.hide()
|
||||
binding.accountSubscribeButton.hide()
|
||||
}
|
||||
|
||||
if(subscribing) {
|
||||
accountSubscribeButton.setIconResource(R.drawable.ic_notifications_active_24dp)
|
||||
accountSubscribeButton.contentDescription = getString(R.string.action_unsubscribe_account)
|
||||
binding.accountSubscribeButton.setIconResource(R.drawable.ic_notifications_active_24dp)
|
||||
binding.accountSubscribeButton.contentDescription = getString(R.string.action_unsubscribe_account)
|
||||
} else {
|
||||
accountSubscribeButton.setIconResource(R.drawable.ic_notifications_24dp)
|
||||
accountSubscribeButton.contentDescription = getString(R.string.action_subscribe_account)
|
||||
binding.accountSubscribeButton.setIconResource(R.drawable.ic_notifications_24dp)
|
||||
binding.accountSubscribeButton.contentDescription = getString(R.string.action_subscribe_account)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -635,27 +634,27 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
if (loadedAccount?.moved == null) {
|
||||
|
||||
accountFollowButton.show()
|
||||
binding.accountFollowButton.show()
|
||||
updateFollowButton()
|
||||
|
||||
if (blocking || viewModel.isSelf) {
|
||||
accountFloatingActionButton.hide()
|
||||
accountMuteButton.hide()
|
||||
accountSubscribeButton.hide()
|
||||
binding.accountFloatingActionButton.hide()
|
||||
binding.accountMuteButton.hide()
|
||||
binding.accountSubscribeButton.hide()
|
||||
} else {
|
||||
accountFloatingActionButton.show()
|
||||
binding.accountFloatingActionButton.show()
|
||||
if (muting)
|
||||
accountMuteButton.show()
|
||||
binding.accountMuteButton.show()
|
||||
else
|
||||
accountMuteButton.hide()
|
||||
binding.accountMuteButton.hide()
|
||||
updateMuteButton()
|
||||
}
|
||||
|
||||
} else {
|
||||
accountFloatingActionButton.hide()
|
||||
accountFollowButton.hide()
|
||||
accountMuteButton.hide()
|
||||
accountSubscribeButton.hide()
|
||||
binding.accountFloatingActionButton.hide()
|
||||
binding.accountFollowButton.hide()
|
||||
binding.accountMuteButton.hide()
|
||||
binding.accountSubscribeButton.hide()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -833,7 +832,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
|
||||
override fun getActionButton(): FloatingActionButton? {
|
||||
return if (!viewModel.isSelf && !blocking) {
|
||||
accountFloatingActionButton
|
||||
binding.accountFloatingActionButton
|
||||
} else null
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,10 @@ package com.keylesspalace.tusky
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import com.keylesspalace.tusky.databinding.ActivityAccountListBinding
|
||||
import com.keylesspalace.tusky.fragment.AccountListFragment
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountListActivity : BaseActivity(), HasAndroidInjector {
|
||||
|
@ -41,12 +41,14 @@ class AccountListActivity : BaseActivity(), HasAndroidInjector {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_account_list)
|
||||
val binding = ActivityAccountListBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
val type = intent.getSerializableExtra(EXTRA_TYPE) as Type
|
||||
val id: String? = intent.getStringExtra(EXTRA_ID)
|
||||
val accountLocked: Boolean = intent.getBooleanExtra(EXTRA_ACCOUNT_LOCKED, false)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
supportActionBar?.apply {
|
||||
when (type) {
|
||||
Type.BLOCKS -> setTitle(R.string.title_blocks)
|
||||
|
@ -63,7 +65,7 @@ class AccountListActivity : BaseActivity(), HasAndroidInjector {
|
|||
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.fragment_container, AccountListFragment.newInstance(type, id))
|
||||
.replace(R.id.fragment_container, AccountListFragment.newInstance(type, id, accountLocked))
|
||||
.commit()
|
||||
}
|
||||
|
||||
|
@ -72,12 +74,15 @@ class AccountListActivity : BaseActivity(), HasAndroidInjector {
|
|||
companion object {
|
||||
private const val EXTRA_TYPE = "type"
|
||||
private const val EXTRA_ID = "id"
|
||||
private const val EXTRA_ACCOUNT_LOCKED = "acc_locked"
|
||||
|
||||
@JvmStatic
|
||||
fun newIntent(context: Context, type: Type, id: String? = null): Intent {
|
||||
@JvmOverloads
|
||||
fun newIntent(context: Context, type: Type, id: String? = null, accountLocked: Boolean = false): Intent {
|
||||
return Intent(context, AccountListActivity::class.java).apply {
|
||||
putExtra(EXTRA_TYPE, type)
|
||||
putExtra(EXTRA_ID, id)
|
||||
putExtra(EXTRA_ACCOUNT_LOCKED, accountLocked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,11 +23,13 @@ import android.view.ViewGroup
|
|||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.databinding.FragmentAccountsInListBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
|
@ -38,9 +40,6 @@ import com.keylesspalace.tusky.viewmodel.State
|
|||
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
||||
import com.uber.autodispose.autoDispose
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.fragment_accounts_in_list.*
|
||||
import kotlinx.android.synthetic.main.item_follow_request.*
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -48,23 +47,11 @@ private typealias AccountInfo = Pair<Account, Boolean>
|
|||
|
||||
class AccountsInListFragment : DialogFragment(), Injectable {
|
||||
|
||||
companion object {
|
||||
private const val LIST_ID_ARG = "listId"
|
||||
private const val LIST_NAME_ARG = "listName"
|
||||
|
||||
@JvmStatic
|
||||
fun newInstance(listId: String, listName: String): AccountsInListFragment {
|
||||
val args = Bundle().apply {
|
||||
putString(LIST_ID_ARG, listId)
|
||||
putString(LIST_NAME_ARG, listName)
|
||||
}
|
||||
return AccountsInListFragment().apply { arguments = args }
|
||||
}
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
lateinit var viewModel: AccountsInListViewModel
|
||||
|
||||
private val viewModel: AccountsInListViewModel by viewModels { viewModelFactory }
|
||||
private val binding by viewBinding(FragmentAccountsInListBinding::bind)
|
||||
|
||||
private lateinit var listId: String
|
||||
private lateinit var listName: String
|
||||
|
@ -79,7 +66,6 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NORMAL, R.style.TuskyDialogFragmentStyle)
|
||||
viewModel = viewModelFactory.create(AccountsInListViewModel::class.java)
|
||||
val args = requireArguments()
|
||||
listId = args.getString(LIST_ID_ARG)!!
|
||||
listName = args.getString(LIST_NAME_ARG)!!
|
||||
|
@ -100,12 +86,11 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
|||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
accountsRecycler.layoutManager = LinearLayoutManager(view.context)
|
||||
accountsRecycler.adapter = adapter
|
||||
binding.accountsRecycler.layoutManager = LinearLayoutManager(view.context)
|
||||
binding.accountsRecycler.adapter = adapter
|
||||
|
||||
accountsSearchRecycler.layoutManager = LinearLayoutManager(view.context)
|
||||
accountsSearchRecycler.adapter = searchAdapter
|
||||
binding.accountsSearchRecycler.layoutManager = LinearLayoutManager(view.context)
|
||||
binding.accountsSearchRecycler.adapter = searchAdapter
|
||||
|
||||
viewModel.state
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
@ -114,15 +99,15 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
|||
adapter.submitList(state.accounts.asRightOrNull() ?: listOf())
|
||||
|
||||
when (state.accounts) {
|
||||
is Either.Right -> messageView.hide()
|
||||
is Either.Right -> binding.messageView.hide()
|
||||
is Either.Left -> handleError(state.accounts.value)
|
||||
}
|
||||
|
||||
setupSearchView(state)
|
||||
}
|
||||
|
||||
searchView.isSubmitButtonEnabled = true
|
||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
binding.searchView.isSubmitButtonEnabled = true
|
||||
binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
viewModel.search(query ?: "")
|
||||
return true
|
||||
|
@ -141,30 +126,30 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
|||
private fun setupSearchView(state: State) {
|
||||
if (state.searchResult == null) {
|
||||
searchAdapter.submitList(listOf())
|
||||
accountsSearchRecycler.hide()
|
||||
accountsRecycler.show()
|
||||
binding.accountsSearchRecycler.hide()
|
||||
binding.accountsRecycler.show()
|
||||
} else {
|
||||
val listAccounts = state.accounts.asRightOrNull() ?: listOf()
|
||||
val newList = state.searchResult.map { acc ->
|
||||
acc to listAccounts.contains(acc)
|
||||
}
|
||||
searchAdapter.submitList(newList)
|
||||
accountsSearchRecycler.show()
|
||||
accountsRecycler.hide()
|
||||
binding.accountsSearchRecycler.show()
|
||||
binding.accountsRecycler.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleError(error: Throwable) {
|
||||
messageView.show()
|
||||
binding.messageView.show()
|
||||
val retryAction = { _: View ->
|
||||
messageView.hide()
|
||||
binding.messageView.hide()
|
||||
viewModel.load(listId)
|
||||
}
|
||||
if (error is IOException) {
|
||||
messageView.setup(R.drawable.elephant_offline,
|
||||
binding.messageView.setup(R.drawable.elephant_offline,
|
||||
R.string.error_network, retryAction)
|
||||
} else {
|
||||
messageView.setup(R.drawable.elephant_error,
|
||||
binding.messageView.setup(R.drawable.elephant_error,
|
||||
R.string.error_generic, retryAction)
|
||||
}
|
||||
}
|
||||
|
@ -187,39 +172,28 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
|||
}
|
||||
}
|
||||
|
||||
inner class Adapter : ListAdapter<Account, Adapter.ViewHolder>(AccountDiffer) {
|
||||
inner class Adapter : ListAdapter<Account, BindingHolder<ItemFollowRequestBinding>>(AccountDiffer) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_follow_request, parent, false)
|
||||
return ViewHolder(view)
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemFollowRequestBinding> {
|
||||
val binding = ItemFollowRequestBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
val holder = BindingHolder(binding)
|
||||
|
||||
binding.notificationTextView.hide()
|
||||
binding.acceptButton.hide()
|
||||
binding.rejectButton.setOnClickListener {
|
||||
onRemoveFromList(getItem(holder.bindingAdapterPosition).id)
|
||||
}
|
||||
binding.rejectButton.contentDescription =
|
||||
binding.root.context.getString(R.string.action_remove_from_list)
|
||||
|
||||
return holder
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
|
||||
View.OnClickListener, LayoutContainer {
|
||||
|
||||
override val containerView = itemView
|
||||
|
||||
init {
|
||||
acceptButton.hide()
|
||||
rejectButton.setOnClickListener(this)
|
||||
rejectButton.contentDescription =
|
||||
itemView.context.getString(R.string.action_remove_from_list)
|
||||
}
|
||||
|
||||
fun bind(account: Account) {
|
||||
displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView, animateEmojis)
|
||||
usernameTextView.text = account.username
|
||||
loadAvatar(account.avatar, avatar, radius, animateAvatar)
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
onRemoveFromList(getItem(adapterPosition).id)
|
||||
}
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemFollowRequestBinding>, position: Int) {
|
||||
val account = getItem(position)
|
||||
holder.binding.displayNameTextView.text = account.name.emojify(account.emojis, holder.binding.displayNameTextView, animateEmojis)
|
||||
holder.binding.usernameTextView.text = account.username
|
||||
loadAvatar(account.avatar, holder.binding.avatar, radius, animateAvatar)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,57 +206,58 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
|||
return oldItem.second == newItem.second
|
||||
&& oldItem.first.deepEquals(newItem.first)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class SearchAdapter : ListAdapter<AccountInfo, SearchAdapter.ViewHolder>(SearchDiffer) {
|
||||
inner class SearchAdapter : ListAdapter<AccountInfo, BindingHolder<ItemFollowRequestBinding>>(SearchDiffer) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_follow_request, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemFollowRequestBinding> {
|
||||
val binding = ItemFollowRequestBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
val holder = BindingHolder(binding)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val (account, inAList) = getItem(position)
|
||||
holder.bind(account, inAList)
|
||||
|
||||
}
|
||||
|
||||
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
|
||||
View.OnClickListener, LayoutContainer {
|
||||
|
||||
override val containerView = itemView
|
||||
|
||||
fun bind(account: Account, inAList: Boolean) {
|
||||
displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView, animateEmojis)
|
||||
usernameTextView.text = account.username
|
||||
loadAvatar(account.avatar, avatar, radius, animateAvatar)
|
||||
|
||||
rejectButton.apply {
|
||||
contentDescription = if (inAList) {
|
||||
setImageResource(R.drawable.ic_reject_24dp)
|
||||
getString(R.string.action_remove_from_list)
|
||||
} else {
|
||||
setImageResource(R.drawable.ic_plus_24dp)
|
||||
getString(R.string.action_add_to_list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
acceptButton.hide()
|
||||
rejectButton.setOnClickListener(this)
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
val (account, inAList) = getItem(adapterPosition)
|
||||
binding.notificationTextView.hide()
|
||||
binding.acceptButton.hide()
|
||||
binding.rejectButton.setOnClickListener {
|
||||
val (account, inAList) = getItem(holder.bindingAdapterPosition)
|
||||
if (inAList) {
|
||||
onRemoveFromList(account.id)
|
||||
} else {
|
||||
onAddToList(account)
|
||||
}
|
||||
}
|
||||
|
||||
return holder
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemFollowRequestBinding>, position: Int) {
|
||||
val (account, inAList) = getItem(position)
|
||||
|
||||
holder.binding.displayNameTextView.text = account.name.emojify(account.emojis, holder.binding.displayNameTextView, animateEmojis)
|
||||
holder.binding.usernameTextView.text = account.username
|
||||
loadAvatar(account.avatar, holder.binding.avatar, radius, animateAvatar)
|
||||
|
||||
holder.binding.rejectButton.apply {
|
||||
contentDescription = if (inAList) {
|
||||
setImageResource(R.drawable.ic_reject_24dp)
|
||||
getString(R.string.action_remove_from_list)
|
||||
} else {
|
||||
setImageResource(R.drawable.ic_plus_24dp)
|
||||
getString(R.string.action_add_to_list)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val LIST_ID_ARG = "listId"
|
||||
private const val LIST_NAME_ARG = "listName"
|
||||
|
||||
@JvmStatic
|
||||
fun newInstance(listId: String, listName: String): AccountsInListFragment {
|
||||
val args = Bundle().apply {
|
||||
putString(LIST_ID_ARG, listId)
|
||||
putString(LIST_NAME_ARG, listName)
|
||||
}
|
||||
return AccountsInListFragment().apply { arguments = args }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ import com.bumptech.glide.load.resource.bitmap.FitCenter
|
|||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
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.*
|
||||
|
@ -47,8 +48,6 @@ import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
|||
import com.mikepenz.iconics.utils.colorInt
|
||||
import com.mikepenz.iconics.utils.sizeDp
|
||||
import com.theartofdev.edmodo.cropper.CropImage
|
||||
import kotlinx.android.synthetic.main.activity_edit_profile.*
|
||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class EditProfileActivity : BaseActivity(), Injectable {
|
||||
|
@ -71,6 +70,8 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
|
||||
private val viewModel: EditProfileViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private val binding by viewBinding(ActivityEditProfileBinding::inflate)
|
||||
|
||||
private var currentlyPicking: PickType = PickType.NOTHING
|
||||
|
||||
private val accountFieldEditAdapter = AccountFieldEditAdapter()
|
||||
|
@ -88,33 +89,33 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
currentlyPicking = PickType.valueOf(it)
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_edit_profile)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
supportActionBar?.run {
|
||||
setTitle(R.string.title_edit_profile)
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowHomeEnabled(true)
|
||||
}
|
||||
|
||||
avatarButton.setOnClickListener { onMediaPick(PickType.AVATAR) }
|
||||
headerButton.setOnClickListener { onMediaPick(PickType.HEADER) }
|
||||
binding.avatarButton.setOnClickListener { onMediaPick(PickType.AVATAR) }
|
||||
binding.headerButton.setOnClickListener { onMediaPick(PickType.HEADER) }
|
||||
|
||||
fieldList.layoutManager = LinearLayoutManager(this)
|
||||
fieldList.adapter = accountFieldEditAdapter
|
||||
binding.fieldList.layoutManager = LinearLayoutManager(this)
|
||||
binding.fieldList.adapter = accountFieldEditAdapter
|
||||
|
||||
val plusDrawable = IconicsDrawable(this, GoogleMaterial.Icon.gmd_add).apply { sizeDp = 12; colorInt = Color.WHITE }
|
||||
|
||||
addFieldButton.setCompoundDrawablesRelativeWithIntrinsicBounds(plusDrawable, null, null, null)
|
||||
binding.addFieldButton.setCompoundDrawablesRelativeWithIntrinsicBounds(plusDrawable, null, null, null)
|
||||
|
||||
addFieldButton.setOnClickListener {
|
||||
binding.addFieldButton.setOnClickListener {
|
||||
accountFieldEditAdapter.addField()
|
||||
if(accountFieldEditAdapter.itemCount >= MAX_ACCOUNT_FIELDS) {
|
||||
it.isVisible = false
|
||||
}
|
||||
|
||||
scrollView.post{
|
||||
scrollView.smoothScrollTo(0, it.bottom)
|
||||
binding.scrollView.post{
|
||||
binding.scrollView.smoothScrollTo(0, it.bottom)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,12 +127,12 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
val me = profileRes.data
|
||||
if (me != null) {
|
||||
|
||||
displayNameEditText.setText(me.displayName)
|
||||
noteEditText.setText(me.source?.note)
|
||||
lockedCheckBox.isChecked = me.locked
|
||||
binding.displayNameEditText.setText(me.displayName)
|
||||
binding.noteEditText.setText(me.source?.note)
|
||||
binding.lockedCheckBox.isChecked = me.locked
|
||||
|
||||
accountFieldEditAdapter.setFields(me.source?.fields ?: emptyList())
|
||||
addFieldButton.isEnabled = me.source?.fields?.size ?: 0 < MAX_ACCOUNT_FIELDS
|
||||
binding.addFieldButton.isEnabled = me.source?.fields?.size ?: 0 < MAX_ACCOUNT_FIELDS
|
||||
|
||||
if(viewModel.avatarData.value == null) {
|
||||
Glide.with(this)
|
||||
|
@ -141,19 +142,19 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
FitCenter(),
|
||||
RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp))
|
||||
)
|
||||
.into(avatarPreview)
|
||||
.into(binding.avatarPreview)
|
||||
}
|
||||
|
||||
if(viewModel.headerData.value == null) {
|
||||
Glide.with(this)
|
||||
.load(me.header)
|
||||
.into(headerPreview)
|
||||
.into(binding.headerPreview)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
is Error -> {
|
||||
val snackbar = Snackbar.make(avatarButton, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
val snackbar = Snackbar.make(binding.avatarButton, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
snackbar.setAction(R.string.action_retry) {
|
||||
viewModel.obtainProfile()
|
||||
}
|
||||
|
@ -169,14 +170,14 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
is Success -> {
|
||||
val instance = result.data
|
||||
if (instance?.maxBioChars != null && instance.maxBioChars > 0) {
|
||||
noteEditTextLayout.counterMaxLength = instance.maxBioChars
|
||||
binding.noteEditTextLayout.counterMaxLength = instance.maxBioChars
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observeImage(viewModel.avatarData, avatarPreview, avatarProgressBar, true)
|
||||
observeImage(viewModel.headerData, headerPreview, headerProgressBar, false)
|
||||
observeImage(viewModel.avatarData, binding.avatarPreview, binding.avatarProgressBar, true)
|
||||
observeImage(viewModel.headerData, binding.headerPreview, binding.headerProgressBar, false)
|
||||
|
||||
viewModel.saveData.observe(this, {
|
||||
when(it) {
|
||||
|
@ -184,7 +185,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
finish()
|
||||
}
|
||||
is Loading -> {
|
||||
saveProgressBar.visibility = View.VISIBLE
|
||||
binding.saveProgressBar.visibility = View.VISIBLE
|
||||
}
|
||||
is Error -> {
|
||||
onSaveFailure(it.errorMessage)
|
||||
|
@ -202,9 +203,9 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
override fun onStop() {
|
||||
super.onStop()
|
||||
if(!isFinishing) {
|
||||
viewModel.updateProfile(displayNameEditText.text.toString(),
|
||||
noteEditText.text.toString(),
|
||||
lockedCheckBox.isChecked,
|
||||
viewModel.updateProfile(binding.displayNameEditText.text.toString(),
|
||||
binding.noteEditText.text.toString(),
|
||||
binding.lockedCheckBox.isChecked,
|
||||
accountFieldEditAdapter.getFieldData())
|
||||
}
|
||||
}
|
||||
|
@ -268,7 +269,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
initiateMediaPicking()
|
||||
} else {
|
||||
endMediaPicking()
|
||||
Snackbar.make(avatarButton, R.string.error_media_upload_permission, Snackbar.LENGTH_LONG).show()
|
||||
Snackbar.make(binding.avatarButton, R.string.error_media_upload_permission, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -309,39 +310,38 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
return
|
||||
}
|
||||
|
||||
viewModel.save(displayNameEditText.text.toString(),
|
||||
noteEditText.text.toString(),
|
||||
lockedCheckBox.isChecked,
|
||||
viewModel.save(binding.displayNameEditText.text.toString(),
|
||||
binding.noteEditText.text.toString(),
|
||||
binding.lockedCheckBox.isChecked,
|
||||
accountFieldEditAdapter.getFieldData(),
|
||||
this)
|
||||
}
|
||||
|
||||
private fun onSaveFailure(msg: String?) {
|
||||
val errorMsg = msg ?: getString(R.string.error_media_upload_sending)
|
||||
Snackbar.make(avatarButton, errorMsg, Snackbar.LENGTH_LONG).show()
|
||||
saveProgressBar.visibility = View.GONE
|
||||
Snackbar.make(binding.avatarButton, errorMsg, Snackbar.LENGTH_LONG).show()
|
||||
binding.saveProgressBar.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun beginMediaPicking() {
|
||||
when (currentlyPicking) {
|
||||
PickType.AVATAR -> {
|
||||
avatarProgressBar.visibility = View.VISIBLE
|
||||
avatarPreview.visibility = View.INVISIBLE
|
||||
avatarButton.setImageDrawable(null)
|
||||
|
||||
binding.avatarProgressBar.visibility = View.VISIBLE
|
||||
binding.avatarPreview.visibility = View.INVISIBLE
|
||||
binding.avatarButton.setImageDrawable(null)
|
||||
}
|
||||
PickType.HEADER -> {
|
||||
headerProgressBar.visibility = View.VISIBLE
|
||||
headerPreview.visibility = View.INVISIBLE
|
||||
headerButton.setImageDrawable(null)
|
||||
binding.headerProgressBar.visibility = View.VISIBLE
|
||||
binding.headerPreview.visibility = View.INVISIBLE
|
||||
binding.headerButton.setImageDrawable(null)
|
||||
}
|
||||
PickType.NOTHING -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
|
||||
private fun endMediaPicking() {
|
||||
avatarProgressBar.visibility = View.GONE
|
||||
headerProgressBar.visibility = View.GONE
|
||||
binding.avatarProgressBar.visibility = View.GONE
|
||||
binding.headerProgressBar.visibility = View.GONE
|
||||
|
||||
currentlyPicking = PickType.NOTHING
|
||||
}
|
||||
|
@ -402,7 +402,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
}
|
||||
|
||||
private fun onResizeFailure() {
|
||||
Snackbar.make(avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG).show()
|
||||
Snackbar.make(binding.avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG).show()
|
||||
endMediaPicking()
|
||||
}
|
||||
|
||||
|
|
|
@ -7,13 +7,13 @@ import android.widget.Toast
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||
import com.keylesspalace.tusky.databinding.ActivityFiltersBinding
|
||||
import com.keylesspalace.tusky.databinding.DialogFilterBinding
|
||||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import kotlinx.android.synthetic.main.activity_filters.*
|
||||
import kotlinx.android.synthetic.main.dialog_filter.*
|
||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
|
@ -28,13 +28,28 @@ class FiltersActivity: BaseActivity() {
|
|||
@Inject
|
||||
lateinit var eventHub: EventHub
|
||||
|
||||
private val binding by viewBinding(ActivityFiltersBinding::inflate)
|
||||
|
||||
private lateinit var context : String
|
||||
private lateinit var filters: MutableList<Filter>
|
||||
private lateinit var dialog: AlertDialog
|
||||
|
||||
companion object {
|
||||
const val FILTERS_CONTEXT = "filters_context"
|
||||
const val FILTERS_TITLE = "filters_title"
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(binding.root)
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
supportActionBar?.run {
|
||||
// Back button
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowHomeEnabled(true)
|
||||
}
|
||||
binding.addFilterButton.setOnClickListener {
|
||||
showAddFilterDialog()
|
||||
}
|
||||
|
||||
title = intent?.getStringExtra(FILTERS_TITLE)
|
||||
context = intent?.getStringExtra(FILTERS_CONTEXT)!!
|
||||
loadFilters()
|
||||
}
|
||||
|
||||
private fun updateFilter(filter: Filter, itemIndex: Int) {
|
||||
|
@ -101,52 +116,51 @@ class FiltersActivity: BaseActivity() {
|
|||
}
|
||||
|
||||
private fun showAddFilterDialog() {
|
||||
dialog = AlertDialog.Builder(this@FiltersActivity)
|
||||
val binding = DialogFilterBinding.inflate(layoutInflater)
|
||||
binding.phraseWholeWord.isChecked = true
|
||||
AlertDialog.Builder(this@FiltersActivity)
|
||||
.setTitle(R.string.filter_addition_dialog_title)
|
||||
.setView(R.layout.dialog_filter)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(android.R.string.ok){ _, _ ->
|
||||
createFilter(dialog.phraseEditText.text.toString(), dialog.phraseWholeWord.isChecked)
|
||||
createFilter(binding.phraseEditText.text.toString(), binding.phraseWholeWord.isChecked)
|
||||
}
|
||||
.setNeutralButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
dialog.show()
|
||||
dialog.phraseWholeWord.isChecked = true
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun setupEditDialogForItem(itemIndex: Int) {
|
||||
dialog = AlertDialog.Builder(this@FiltersActivity)
|
||||
val binding = DialogFilterBinding.inflate(layoutInflater)
|
||||
val filter = filters[itemIndex]
|
||||
binding.phraseEditText.setText(filter.phrase)
|
||||
binding.phraseWholeWord.isChecked = filter.wholeWord
|
||||
|
||||
AlertDialog.Builder(this@FiltersActivity)
|
||||
.setTitle(R.string.filter_edit_dialog_title)
|
||||
.setView(R.layout.dialog_filter)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(R.string.filter_dialog_update_button) { _, _ ->
|
||||
val oldFilter = filters[itemIndex]
|
||||
val newFilter = Filter(oldFilter.id, dialog.phraseEditText.text.toString(), oldFilter.context,
|
||||
oldFilter.expiresAt, oldFilter.irreversible, dialog.phraseWholeWord.isChecked)
|
||||
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)
|
||||
.create()
|
||||
dialog.show()
|
||||
|
||||
// Need to show the dialog before referencing any elements from its view
|
||||
val filter = filters[itemIndex]
|
||||
dialog.phraseEditText.setText(filter.phrase)
|
||||
dialog.phraseWholeWord.isChecked = filter.wholeWord
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun refreshFilterDisplay() {
|
||||
filtersView.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, filters.map { filter -> filter.phrase })
|
||||
filtersView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ -> setupEditDialogForItem(position) }
|
||||
binding.filtersView.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, filters.map { filter -> filter.phrase })
|
||||
binding.filtersView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ -> setupEditDialogForItem(position) }
|
||||
}
|
||||
|
||||
private fun loadFilters() {
|
||||
|
||||
filterMessageView.hide()
|
||||
filtersView.hide()
|
||||
addFilterButton.hide()
|
||||
filterProgressBar.show()
|
||||
binding.filterMessageView.hide()
|
||||
binding.filtersView.hide()
|
||||
binding.addFilterButton.hide()
|
||||
binding.filterProgressBar.show()
|
||||
|
||||
api.getFilters().enqueue(object : Callback<List<Filter>> {
|
||||
override fun onResponse(call: Call<List<Filter>>, response: Response<List<Filter>>) {
|
||||
|
@ -156,52 +170,33 @@ class FiltersActivity: BaseActivity() {
|
|||
filters = filterResponse.filter { filter -> filter.context.contains(context) }.toMutableList()
|
||||
refreshFilterDisplay()
|
||||
|
||||
filtersView.show()
|
||||
addFilterButton.show()
|
||||
filterProgressBar.hide()
|
||||
binding.filtersView.show()
|
||||
binding.addFilterButton.show()
|
||||
binding.filterProgressBar.hide()
|
||||
} else {
|
||||
filterProgressBar.hide()
|
||||
filterMessageView.show()
|
||||
filterMessageView.setup(R.drawable.elephant_error,
|
||||
binding.filterProgressBar.hide()
|
||||
binding.filterMessageView.show()
|
||||
binding.filterMessageView.setup(R.drawable.elephant_error,
|
||||
R.string.error_generic) { loadFilters() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<List<Filter>>, t: Throwable) {
|
||||
filterProgressBar.hide()
|
||||
filterMessageView.show()
|
||||
binding.filterProgressBar.hide()
|
||||
binding.filterMessageView.show()
|
||||
if (t is IOException) {
|
||||
filterMessageView.setup(R.drawable.elephant_offline,
|
||||
binding.filterMessageView.setup(R.drawable.elephant_offline,
|
||||
R.string.error_network) { loadFilters() }
|
||||
} else {
|
||||
filterMessageView.setup(R.drawable.elephant_error,
|
||||
binding.filterMessageView.setup(R.drawable.elephant_error,
|
||||
R.string.error_generic) { loadFilters() }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_filters)
|
||||
setupToolbarBackArrow()
|
||||
addFilterButton.setOnClickListener {
|
||||
showAddFilterDialog()
|
||||
}
|
||||
|
||||
title = intent?.getStringExtra(FILTERS_TITLE)
|
||||
context = intent?.getStringExtra(FILTERS_CONTEXT)!!
|
||||
loadFilters()
|
||||
companion object {
|
||||
const val FILTERS_CONTEXT = "filters_context"
|
||||
const val FILTERS_TITLE = "filters_title"
|
||||
}
|
||||
|
||||
private fun setupToolbarBackArrow() {
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.run {
|
||||
// Back button
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowHomeEnabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -19,23 +19,20 @@ import android.os.Bundle
|
|||
import androidx.annotation.RawRes
|
||||
import android.util.Log
|
||||
import android.widget.TextView
|
||||
import com.keylesspalace.tusky.databinding.ActivityLicenseBinding
|
||||
import com.keylesspalace.tusky.util.IOUtils
|
||||
import kotlinx.android.extensions.CacheImplementation
|
||||
import kotlinx.android.extensions.ContainerOptions
|
||||
import kotlinx.android.synthetic.main.activity_license.*
|
||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
|
||||
class LicenseActivity : BaseActivity() {
|
||||
|
||||
@ContainerOptions(cache = CacheImplementation.NO_CACHE)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_license)
|
||||
val binding = ActivityLicenseBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
supportActionBar?.run {
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowHomeEnabled(true)
|
||||
|
@ -43,7 +40,7 @@ class LicenseActivity : BaseActivity() {
|
|||
|
||||
setTitle(R.string.title_licenses)
|
||||
|
||||
loadFileIntoTextView(R.raw.apache, licenseApacheTextView)
|
||||
loadFileIntoTextView(R.raw.apache, binding.licenseApacheTextView)
|
||||
|
||||
}
|
||||
|
||||
|
@ -67,7 +64,5 @@ class LicenseActivity : BaseActivity() {
|
|||
IOUtils.closeQuietly(br)
|
||||
|
||||
textView.text = sb.toString()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,12 +24,14 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.*
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import at.connyduck.sparkbutton.helpers.Utils
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.keylesspalace.tusky.databinding.ActivityListsBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.entity.MastoList
|
||||
|
@ -47,8 +49,6 @@ import com.uber.autodispose.autoDispose
|
|||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.android.synthetic.main.activity_lists.*
|
||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -57,47 +57,42 @@ import javax.inject.Inject
|
|||
|
||||
class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newIntent(context: Context): Intent {
|
||||
return Intent(context, ListsActivity::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
@Inject
|
||||
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
|
||||
|
||||
private lateinit var viewModel: ListsViewModel
|
||||
private val viewModel: ListsViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private val binding by viewBinding(ActivityListsBinding::inflate)
|
||||
|
||||
private val adapter = ListsAdapter()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_lists)
|
||||
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
supportActionBar?.apply {
|
||||
title = getString(R.string.title_lists)
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowHomeEnabled(true)
|
||||
}
|
||||
|
||||
listsRecycler.adapter = adapter
|
||||
listsRecycler.layoutManager = LinearLayoutManager(this)
|
||||
listsRecycler.addItemDecoration(
|
||||
binding.listsRecycler.adapter = adapter
|
||||
binding.listsRecycler.layoutManager = LinearLayoutManager(this)
|
||||
binding.listsRecycler.addItemDecoration(
|
||||
DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
|
||||
|
||||
viewModel = viewModelFactory.create(ListsViewModel::class.java)
|
||||
viewModel.state
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this))
|
||||
.subscribe(this::update)
|
||||
viewModel.retryLoading()
|
||||
|
||||
addListButton.setOnClickListener {
|
||||
binding.addListButton.setOnClickListener {
|
||||
showlistNameDialog(null)
|
||||
}
|
||||
|
||||
|
@ -153,37 +148,36 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
|
||||
private fun update(state: ListsViewModel.State) {
|
||||
adapter.submitList(state.lists)
|
||||
progressBar.visible(state.loadingState == LOADING)
|
||||
binding.progressBar.visible(state.loadingState == LOADING)
|
||||
when (state.loadingState) {
|
||||
INITIAL, LOADING -> messageView.hide()
|
||||
INITIAL, LOADING -> binding.messageView.hide()
|
||||
ERROR_NETWORK -> {
|
||||
messageView.show()
|
||||
messageView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||
binding.messageView.show()
|
||||
binding.messageView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||
viewModel.retryLoading()
|
||||
}
|
||||
}
|
||||
ERROR_OTHER -> {
|
||||
messageView.show()
|
||||
messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||
binding.messageView.show()
|
||||
binding.messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||
viewModel.retryLoading()
|
||||
}
|
||||
}
|
||||
LOADED ->
|
||||
if (state.lists.isEmpty()) {
|
||||
messageView.show()
|
||||
messageView.setup(R.drawable.elephant_friend_empty, R.string.message_empty,
|
||||
binding.messageView.show()
|
||||
binding.messageView.setup(R.drawable.elephant_friend_empty, R.string.message_empty,
|
||||
null)
|
||||
} else {
|
||||
messageView.hide()
|
||||
binding.messageView.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showMessage(@StringRes messageId: Int) {
|
||||
Snackbar.make(
|
||||
listsRecycler, messageId, Snackbar.LENGTH_SHORT
|
||||
binding.listsRecycler, messageId, Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
|
||||
}
|
||||
|
||||
private fun onListSelected(listId: String) {
|
||||
|
@ -215,8 +209,6 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
}
|
||||
}
|
||||
|
||||
override fun androidInjector() = dispatchingAndroidInjector
|
||||
|
||||
private object ListsDiffer : DiffUtil.ItemCallback<MastoList>() {
|
||||
override fun areItemsTheSame(oldItem: MastoList, newItem: MastoList): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
|
@ -258,9 +250,9 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
|
||||
override fun onClick(v: View) {
|
||||
if (v == itemView) {
|
||||
onListSelected(getItem(adapterPosition).id)
|
||||
onListSelected(getItem(bindingAdapterPosition).id)
|
||||
} else {
|
||||
onMore(getItem(adapterPosition), v)
|
||||
onMore(getItem(bindingAdapterPosition), v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -273,4 +265,10 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
viewModel.renameList(listId, name.toString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun androidInjector() = dispatchingAndroidInjector
|
||||
|
||||
companion object {
|
||||
fun newIntent(context: Context) = Intent(context, ListsActivity::class.java)
|
||||
}
|
||||
}
|
|
@ -29,15 +29,12 @@ import androidx.appcompat.app.AlertDialog
|
|||
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import com.bumptech.glide.Glide
|
||||
import com.keylesspalace.tusky.databinding.ActivityLoginBinding
|
||||
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.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.getNonNullString
|
||||
import com.keylesspalace.tusky.util.rickRoll
|
||||
import com.keylesspalace.tusky.util.shouldRickRoll
|
||||
import kotlinx.android.synthetic.main.activity_login.*
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import okhttp3.HttpUrl
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
|
@ -49,6 +46,8 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
@Inject
|
||||
lateinit var mastodonApi: MastodonApi
|
||||
|
||||
private val binding by viewBinding(ActivityLoginBinding::inflate)
|
||||
|
||||
private lateinit var preferences: SharedPreferences
|
||||
|
||||
private val oauthRedirectUri: String
|
||||
|
@ -61,26 +60,26 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_login)
|
||||
setContentView(binding.root)
|
||||
|
||||
if(savedInstanceState == null && BuildConfig.CUSTOM_INSTANCE.isNotBlank() && !isAdditionalLogin()) {
|
||||
domainEditText.setText(BuildConfig.CUSTOM_INSTANCE)
|
||||
domainEditText.setSelection(BuildConfig.CUSTOM_INSTANCE.length)
|
||||
binding.domainEditText.setText(BuildConfig.CUSTOM_INSTANCE)
|
||||
binding.domainEditText.setSelection(BuildConfig.CUSTOM_INSTANCE.length)
|
||||
}
|
||||
|
||||
if(BuildConfig.CUSTOM_LOGO_URL.isNotBlank()) {
|
||||
Glide.with(loginLogo)
|
||||
Glide.with(binding.loginLogo)
|
||||
.load(BuildConfig.CUSTOM_LOGO_URL)
|
||||
.placeholder(null)
|
||||
.into(loginLogo)
|
||||
.into(binding.loginLogo)
|
||||
}
|
||||
|
||||
preferences = getSharedPreferences(
|
||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE)
|
||||
|
||||
loginButton.setOnClickListener { onButtonClick() }
|
||||
binding.loginButton.setOnClickListener { onButtonClick() }
|
||||
|
||||
whatsAnInstanceTextView.setOnClickListener {
|
||||
binding.whatsAnInstanceTextView.setOnClickListener {
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setMessage(R.string.dialog_whats_an_instance)
|
||||
.setPositiveButton(R.string.action_close, null)
|
||||
|
@ -90,11 +89,11 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
}
|
||||
|
||||
if (isAdditionalLogin()) {
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false)
|
||||
} else {
|
||||
toolbar.visibility = View.GONE
|
||||
binding.toolbar.visibility = View.GONE
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -117,15 +116,15 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
*/
|
||||
private fun onButtonClick() {
|
||||
|
||||
loginButton.isEnabled = false
|
||||
binding.loginButton.isEnabled = false
|
||||
|
||||
val domain = canonicalizeDomain(domainEditText.text.toString())
|
||||
val domain = canonicalizeDomain(binding.domainEditText.text.toString())
|
||||
|
||||
try {
|
||||
HttpUrl.Builder().host(domain).scheme("https").build()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
setLoading(false)
|
||||
domainTextInputLayout.error = getString(R.string.error_invalid_domain)
|
||||
binding.domainTextInputLayout.error = getString(R.string.error_invalid_domain)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -138,8 +137,8 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
override fun onResponse(call: Call<AppCredentials>,
|
||||
response: Response<AppCredentials>) {
|
||||
if (!response.isSuccessful) {
|
||||
loginButton.isEnabled = true
|
||||
domainTextInputLayout.error = getString(R.string.error_failed_app_registration)
|
||||
binding.loginButton.isEnabled = true
|
||||
binding.domainTextInputLayout.error = getString(R.string.error_failed_app_registration)
|
||||
setLoading(false)
|
||||
Log.e(TAG, "App authentication failed. " + response.message())
|
||||
return
|
||||
|
@ -158,8 +157,8 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
}
|
||||
|
||||
override fun onFailure(call: Call<AppCredentials>, t: Throwable) {
|
||||
loginButton.isEnabled = true
|
||||
domainTextInputLayout.error = getString(R.string.error_failed_app_registration)
|
||||
binding.loginButton.isEnabled = true
|
||||
binding.domainTextInputLayout.error = getString(R.string.error_failed_app_registration)
|
||||
setLoading(false)
|
||||
Log.e(TAG, Log.getStackTraceString(t))
|
||||
}
|
||||
|
@ -190,7 +189,7 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
if (viewIntent.resolveActivity(packageManager) != null) {
|
||||
startActivity(viewIntent)
|
||||
} else {
|
||||
domainEditText.error = getString(R.string.error_no_web_browser_found)
|
||||
binding.domainEditText.error = getString(R.string.error_no_web_browser_found)
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
@ -224,7 +223,7 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
onLoginSuccess(response.body()!!.accessToken, domain)
|
||||
} else {
|
||||
setLoading(false)
|
||||
domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
|
||||
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()))
|
||||
|
@ -233,7 +232,7 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
|
||||
override fun onFailure(call: Call<AccessToken>, t: Throwable) {
|
||||
setLoading(false)
|
||||
domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
|
||||
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))
|
||||
|
@ -246,14 +245,14 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
/* Authorization failed. Put the error response where the user can read it and they
|
||||
* can try again. */
|
||||
setLoading(false)
|
||||
domainTextInputLayout.error = getString(R.string.error_authorization_denied)
|
||||
binding.domainTextInputLayout.error = getString(R.string.error_authorization_denied)
|
||||
Log.e(TAG, String.format("%s %s",
|
||||
getString(R.string.error_authorization_denied),
|
||||
error))
|
||||
} else {
|
||||
// This case means a junk response was received somehow.
|
||||
setLoading(false)
|
||||
domainTextInputLayout.error = getString(R.string.error_authorization_unknown)
|
||||
binding.domainTextInputLayout.error = getString(R.string.error_authorization_unknown)
|
||||
}
|
||||
} else {
|
||||
// first show or user cancelled login
|
||||
|
@ -263,12 +262,12 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
|
||||
private fun setLoading(loadingState: Boolean) {
|
||||
if (loadingState) {
|
||||
loginLoadingLayout.visibility = View.VISIBLE
|
||||
loginInputLayout.visibility = View.GONE
|
||||
binding.loginLoadingLayout.visibility = View.VISIBLE
|
||||
binding.loginInputLayout.visibility = View.GONE
|
||||
} else {
|
||||
loginLoadingLayout.visibility = View.GONE
|
||||
loginInputLayout.visibility = View.VISIBLE
|
||||
loginButton.isEnabled = true
|
||||
binding.loginLoadingLayout.visibility = View.GONE
|
||||
binding.loginInputLayout.visibility = View.VISIBLE
|
||||
binding.loginButton.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ import com.keylesspalace.tusky.components.notifications.NotificationHelper
|
|||
import com.keylesspalace.tusky.components.preference.PreferencesActivity
|
||||
import com.keylesspalace.tusky.components.scheduled.ScheduledTootActivity
|
||||
import com.keylesspalace.tusky.components.search.SearchActivity
|
||||
import com.keylesspalace.tusky.databinding.ActivityMainBinding
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
import com.keylesspalace.tusky.db.AppDatabase
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
|
@ -86,7 +87,6 @@ import dagger.android.DispatchingAndroidInjector
|
|||
import dagger.android.HasAndroidInjector
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInjector {
|
||||
|
@ -108,6 +108,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
@Inject
|
||||
lateinit var draftHelper: DraftHelper
|
||||
|
||||
private val binding by viewBinding(ActivityMainBinding::inflate)
|
||||
|
||||
private lateinit var header: AccountHeaderView
|
||||
|
||||
private var notificationTabPosition = 0
|
||||
|
@ -119,6 +121,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
|
||||
private lateinit var glide: RequestManager
|
||||
|
||||
private var accountLocked: Boolean = false
|
||||
|
||||
private val emojiInitCallback = object : InitCallback() {
|
||||
override fun onInitialized() {
|
||||
if (!isDestroyed) {
|
||||
|
@ -173,27 +177,27 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
}
|
||||
})
|
||||
}
|
||||
} else if (accountRequested) {
|
||||
// user clicked a notification, show notification tab and switch user if necessary
|
||||
} else if (accountRequested && savedInstanceState == null) {
|
||||
// user clicked a notification, show notification tab
|
||||
showNotificationTab = true
|
||||
}
|
||||
}
|
||||
window.statusBarColor = Color.TRANSPARENT // don't draw a status bar, the DrawerLayout and the MaterialDrawerLayout have their own
|
||||
setContentView(R.layout.activity_main)
|
||||
setContentView(binding.root)
|
||||
|
||||
glide = Glide.with(this)
|
||||
|
||||
composeButton.setOnClickListener {
|
||||
binding.composeButton.setOnClickListener {
|
||||
val composeIntent = Intent(applicationContext, ComposeActivity::class.java)
|
||||
startActivity(composeIntent)
|
||||
}
|
||||
|
||||
val hideTopToolbar = preferences.getBoolean(PrefKeys.HIDE_TOP_TOOLBAR, false)
|
||||
mainToolbar.visible(!hideTopToolbar)
|
||||
binding.mainToolbar.visible(!hideTopToolbar)
|
||||
|
||||
loadDrawerAvatar(activeAccount.profilePictureUrl, true)
|
||||
|
||||
mainToolbar.menu.add(R.string.action_search).apply {
|
||||
binding.mainToolbar.menu.add(R.string.action_search).apply {
|
||||
setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
|
||||
icon = IconicsDrawable(this@MainActivity, GoogleMaterial.Icon.gmd_search).apply {
|
||||
sizeDp = 20
|
||||
|
@ -249,11 +253,11 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
|
||||
override fun onBackPressed() {
|
||||
when {
|
||||
mainDrawerLayout.isOpen -> {
|
||||
mainDrawerLayout.close()
|
||||
binding.mainDrawerLayout.isOpen -> {
|
||||
binding.mainDrawerLayout.close()
|
||||
}
|
||||
viewPager.currentItem != 0 -> {
|
||||
viewPager.currentItem = 0
|
||||
binding.viewPager.currentItem != 0 -> {
|
||||
binding.viewPager.currentItem = 0
|
||||
}
|
||||
else -> {
|
||||
super.onBackPressed()
|
||||
|
@ -264,10 +268,10 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||
when (keyCode) {
|
||||
KeyEvent.KEYCODE_MENU -> {
|
||||
if (mainDrawerLayout.isOpen) {
|
||||
mainDrawerLayout.close()
|
||||
if (binding.mainDrawerLayout.isOpen) {
|
||||
binding.mainDrawerLayout.close()
|
||||
} else {
|
||||
mainDrawerLayout.open()
|
||||
binding.mainDrawerLayout.open()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -319,8 +323,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
|
||||
private fun setupDrawer(savedInstanceState: Bundle?, addSearchButton: Boolean) {
|
||||
|
||||
mainToolbar.setNavigationOnClickListener {
|
||||
mainDrawerLayout.open()
|
||||
binding.mainToolbar.setNavigationOnClickListener {
|
||||
binding.mainDrawerLayout.open()
|
||||
}
|
||||
|
||||
header = AccountHeaderView(this).apply {
|
||||
|
@ -333,7 +337,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
descriptionRes = R.string.add_account_description
|
||||
iconicsIcon = GoogleMaterial.Icon.gmd_add
|
||||
}, 0)
|
||||
attachToSliderView(mainDrawer)
|
||||
attachToSliderView(binding.mainDrawer)
|
||||
dividerBelowHeader = false
|
||||
closeDrawerOnProfileListClick = true
|
||||
}
|
||||
|
@ -369,7 +373,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
}
|
||||
})
|
||||
|
||||
mainDrawer.apply {
|
||||
binding.mainDrawer.apply {
|
||||
tintStatusBar = true
|
||||
addItems(
|
||||
primaryDrawerItem {
|
||||
|
@ -397,6 +401,14 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
startActivityWithSlideInAnimation(intent)
|
||||
}
|
||||
},
|
||||
primaryDrawerItem {
|
||||
nameRes = R.string.action_view_follow_requests
|
||||
iconicsIcon = GoogleMaterial.Icon.gmd_person_add
|
||||
onClick = {
|
||||
val intent = AccountListActivity.newIntent(context, AccountListActivity.Type.FOLLOW_REQUESTS, accountLocked = accountLocked)
|
||||
startActivityWithSlideInAnimation(intent)
|
||||
}
|
||||
},
|
||||
primaryDrawerItem {
|
||||
nameRes = R.string.action_lists
|
||||
iconicsIcon = GoogleMaterial.Icon.gmd_list
|
||||
|
@ -464,7 +476,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
)
|
||||
|
||||
if (addSearchButton) {
|
||||
mainDrawer.addItemsAtPosition(4,
|
||||
binding.mainDrawer.addItemsAtPosition(4,
|
||||
primaryDrawerItem {
|
||||
nameRes = R.string.action_search
|
||||
iconicsIcon = GoogleMaterial.Icon.gmd_search
|
||||
|
@ -478,7 +490,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
}
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
mainDrawer.addItems(
|
||||
binding.mainDrawer.addItems(
|
||||
secondaryDrawerItem {
|
||||
nameText = "debug"
|
||||
isEnabled = false
|
||||
|
@ -490,7 +502,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(mainDrawer.saveInstanceState(outState))
|
||||
super.onSaveInstanceState(binding.mainDrawer.saveInstanceState(outState))
|
||||
}
|
||||
|
||||
private fun setupTabs(selectNotificationTab: Boolean) {
|
||||
|
@ -498,21 +510,21 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
val activeTabLayout = if (preferences.getString("mainNavPosition", "top") == "bottom") {
|
||||
val actionBarSize = ThemeUtils.getDimension(this, R.attr.actionBarSize)
|
||||
val fabMargin = resources.getDimensionPixelSize(R.dimen.fabMargin)
|
||||
(composeButton.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin = actionBarSize + fabMargin
|
||||
tabLayout.hide()
|
||||
bottomTabLayout
|
||||
(binding.composeButton.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin = actionBarSize + fabMargin
|
||||
binding.tabLayout.hide()
|
||||
binding.bottomTabLayout
|
||||
} else {
|
||||
bottomNav.hide()
|
||||
(viewPager.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin = 0
|
||||
(composeButton.layoutParams as CoordinatorLayout.LayoutParams).anchorId = R.id.viewPager
|
||||
tabLayout
|
||||
binding.bottomNav.hide()
|
||||
(binding.viewPager.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin = 0
|
||||
(binding.composeButton.layoutParams as CoordinatorLayout.LayoutParams).anchorId = R.id.viewPager
|
||||
binding.tabLayout
|
||||
}
|
||||
|
||||
val tabs = accountManager.activeAccount!!.tabPreferences
|
||||
|
||||
val adapter = MainPagerAdapter(tabs, this)
|
||||
viewPager.adapter = adapter
|
||||
TabLayoutMediator(activeTabLayout, viewPager) { _: TabLayout.Tab?, _: Int -> }.attach()
|
||||
binding.viewPager.adapter = adapter
|
||||
TabLayoutMediator(activeTabLayout, binding.viewPager) { _: TabLayout.Tab?, _: Int -> }.attach()
|
||||
activeTabLayout.removeAllTabs()
|
||||
for (i in tabs.indices) {
|
||||
val tab = activeTabLayout.newTab()
|
||||
|
@ -533,10 +545,10 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
}
|
||||
|
||||
val pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin)
|
||||
viewPager.setPageTransformer(MarginPageTransformer(pageMargin))
|
||||
binding.viewPager.setPageTransformer(MarginPageTransformer(pageMargin))
|
||||
|
||||
val enableSwipeForTabs = preferences.getBoolean("enableSwipeForTabs", true)
|
||||
viewPager.isUserInputEnabled = enableSwipeForTabs
|
||||
binding.viewPager.isUserInputEnabled = enableSwipeForTabs
|
||||
|
||||
onTabSelectedListener?.let {
|
||||
activeTabLayout.removeOnTabSelectedListener(it)
|
||||
|
@ -548,7 +560,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
NotificationHelper.clearNotificationsForActiveAccount(this@MainActivity, accountManager)
|
||||
}
|
||||
|
||||
mainToolbar.title = tabs[tab.position].title(this@MainActivity)
|
||||
binding.mainToolbar.title = tabs[tab.position].title(this@MainActivity)
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) {}
|
||||
|
@ -564,8 +576,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
}
|
||||
|
||||
val activeTabPosition = if (selectNotificationTab) notificationTabPosition else 0
|
||||
mainToolbar.title = tabs[activeTabPosition].title(this@MainActivity)
|
||||
mainToolbar.setOnClickListener {
|
||||
binding.mainToolbar.title = tabs[activeTabPosition].title(this@MainActivity)
|
||||
binding.mainToolbar.setOnClickListener {
|
||||
(adapter.getFragment(activeTabLayout.selectedTabPosition) as? ReselectableFragment)?.onReselect()
|
||||
}
|
||||
|
||||
|
@ -658,22 +670,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
accountManager.updateActiveAccount(me)
|
||||
NotificationHelper.createNotificationChannelsForAccount(accountManager.activeAccount!!, this)
|
||||
|
||||
// Show follow requests in the menu, if this is a locked account.
|
||||
if (me.locked && mainDrawer.getDrawerItem(DRAWER_ITEM_FOLLOW_REQUESTS) == null) {
|
||||
val followRequestsItem = primaryDrawerItem {
|
||||
identifier = DRAWER_ITEM_FOLLOW_REQUESTS
|
||||
nameRes = R.string.action_view_follow_requests
|
||||
iconicsIcon = GoogleMaterial.Icon.gmd_person_add
|
||||
onClick = {
|
||||
val intent = Intent(this@MainActivity, AccountListActivity::class.java)
|
||||
intent.putExtra("type", AccountListActivity.Type.FOLLOW_REQUESTS)
|
||||
startActivityWithSlideInAnimation(intent)
|
||||
}
|
||||
}
|
||||
mainDrawer.addItemAtPosition(4, followRequestsItem)
|
||||
} else if (!me.locked) {
|
||||
mainDrawer.removeItems(DRAWER_ITEM_FOLLOW_REQUESTS)
|
||||
}
|
||||
accountLocked = me.locked
|
||||
|
||||
updateProfiles()
|
||||
updateShortcut(this, accountManager.activeAccount!!)
|
||||
}
|
||||
|
@ -684,7 +682,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
glide.asDrawable()
|
||||
.load(avatarUrl)
|
||||
.transform(
|
||||
RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_36dp))
|
||||
RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_36dp))
|
||||
)
|
||||
.apply {
|
||||
if (showPlaceholder) {
|
||||
|
@ -695,16 +693,17 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
|
||||
override fun onLoadStarted(placeholder: Drawable?) {
|
||||
if (placeholder != null) {
|
||||
mainToolbar.navigationIcon = FixedSizeDrawable(placeholder, navIconSize, navIconSize)
|
||||
binding.mainToolbar.navigationIcon = FixedSizeDrawable(placeholder, navIconSize, navIconSize)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||
mainToolbar.navigationIcon = FixedSizeDrawable(resource, navIconSize, navIconSize)
|
||||
binding.mainToolbar.navigationIcon = FixedSizeDrawable(resource, navIconSize, navIconSize)
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {
|
||||
if (placeholder != null) {
|
||||
mainToolbar.navigationIcon = FixedSizeDrawable(placeholder, navIconSize, navIconSize)
|
||||
binding.mainToolbar.navigationIcon = FixedSizeDrawable(placeholder, navIconSize, navIconSize)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -726,7 +725,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
}
|
||||
|
||||
private fun updateAnnouncementsBadge() {
|
||||
mainDrawer.updateBadge(DRAWER_ITEM_ANNOUNCEMENTS, StringHolder(if (unreadAnnouncementsCount <= 0) null else unreadAnnouncementsCount.toString()))
|
||||
binding.mainDrawer.updateBadge(DRAWER_ITEM_ANNOUNCEMENTS, StringHolder(if (unreadAnnouncementsCount <= 0) null else unreadAnnouncementsCount.toString()))
|
||||
}
|
||||
|
||||
private fun updateProfiles() {
|
||||
|
@ -779,14 +778,13 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
|
||||
}
|
||||
|
||||
override fun getActionButton(): FloatingActionButton? = composeButton
|
||||
override fun getActionButton(): FloatingActionButton? = binding.composeButton
|
||||
|
||||
override fun androidInjector() = androidInjector
|
||||
|
||||
companion object {
|
||||
private const val TAG = "MainActivity" // logging tag
|
||||
private const val DRAWER_ITEM_ADD_ACCOUNT: Long = -13
|
||||
private const val DRAWER_ITEM_FOLLOW_REQUESTS: Long = 10
|
||||
private const val DRAWER_ITEM_ANNOUNCEMENTS: Long = 14
|
||||
const val STATUS_URL = "statusUrl"
|
||||
}
|
||||
|
|
|
@ -4,43 +4,28 @@ 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.fragment.TimelineFragment
|
||||
import com.keylesspalace.tusky.interfaces.ActionButtonActivity
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInjector {
|
||||
|
||||
companion object {
|
||||
private const val ARG_KIND = "kind"
|
||||
private const val ARG_ARG = "arg"
|
||||
|
||||
@JvmStatic
|
||||
fun newIntent(context: Context, kind: TimelineFragment.Kind,
|
||||
argument: String?): Intent {
|
||||
val intent = Intent(context, ModalTimelineActivity::class.java)
|
||||
intent.putExtra(ARG_KIND, kind)
|
||||
intent.putExtra(ARG_ARG, argument)
|
||||
return intent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_modal_timeline)
|
||||
val binding = ActivityModalTimelineBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
val bar = supportActionBar
|
||||
if (bar != null) {
|
||||
bar.title = getString(R.string.title_list_timeline)
|
||||
bar.setDisplayHomeAsUpEnabled(true)
|
||||
bar.setDisplayShowHomeEnabled(true)
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
supportActionBar?.apply {
|
||||
title = getString(R.string.title_list_timeline)
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowHomeEnabled(true)
|
||||
}
|
||||
|
||||
if (supportFragmentManager.findFragmentById(R.id.contentFrame) == null) {
|
||||
|
@ -57,4 +42,18 @@ class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasAn
|
|||
|
||||
override fun androidInjector() = dispatchingAndroidInjector
|
||||
|
||||
companion object {
|
||||
private const val ARG_KIND = "kind"
|
||||
private const val ARG_ARG = "arg"
|
||||
|
||||
@JvmStatic
|
||||
fun newIntent(context: Context, kind: TimelineFragment.Kind,
|
||||
argument: String?): Intent {
|
||||
val intent = Intent(context, ModalTimelineActivity::class.java)
|
||||
intent.putExtra(ARG_KIND, kind)
|
||||
intent.putExtra(ARG_ARG, argument)
|
||||
return intent
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ 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.fragment.TimelineFragment
|
||||
import com.keylesspalace.tusky.fragment.TimelineFragment.Kind
|
||||
|
@ -27,9 +28,6 @@ import javax.inject.Inject
|
|||
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import kotlinx.android.extensions.CacheImplementation
|
||||
import kotlinx.android.extensions.ContainerOptions
|
||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||
|
||||
class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
||||
|
||||
|
@ -39,12 +37,12 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
private val kind: Kind
|
||||
get() = Kind.valueOf(intent.getStringExtra(EXTRA_KIND)!!)
|
||||
|
||||
@ContainerOptions(cache = CacheImplementation.NO_CACHE)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_statuslist)
|
||||
val binding = ActivityStatuslistBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
|
||||
val title = if(kind == Kind.FAVOURITES) {
|
||||
R.string.title_favourites
|
||||
|
|
|
@ -38,17 +38,17 @@ import com.keylesspalace.tusky.adapter.ListSelectionAdapter
|
|||
import com.keylesspalace.tusky.adapter.TabAdapter
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.MainTabsChangedEvent
|
||||
import com.keylesspalace.tusky.databinding.ActivityTabPreferenceBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.onTextChanged
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
||||
import com.uber.autodispose.autoDispose
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.android.synthetic.main.activity_tab_preference.*
|
||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||
import java.util.regex.Pattern
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -59,6 +59,8 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
@Inject
|
||||
lateinit var eventHub: EventHub
|
||||
|
||||
private val binding by viewBinding(ActivityTabPreferenceBinding::inflate)
|
||||
|
||||
private lateinit var currentTabs: MutableList<TabData>
|
||||
private lateinit var currentTabsAdapter: TabAdapter
|
||||
private lateinit var touchHelper: ItemTouchHelper
|
||||
|
@ -73,9 +75,9 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_tab_preference)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
|
||||
supportActionBar?.apply {
|
||||
setTitle(R.string.title_tab_preferences)
|
||||
|
@ -85,13 +87,13 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
|
||||
currentTabs = accountManager.activeAccount?.tabPreferences.orEmpty().toMutableList()
|
||||
currentTabsAdapter = TabAdapter(currentTabs, false, this, currentTabs.size <= MIN_TAB_COUNT)
|
||||
currentTabsRecyclerView.adapter = currentTabsAdapter
|
||||
currentTabsRecyclerView.layoutManager = LinearLayoutManager(this)
|
||||
currentTabsRecyclerView.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
|
||||
binding.currentTabsRecyclerView.adapter = currentTabsAdapter
|
||||
binding.currentTabsRecyclerView.layoutManager = LinearLayoutManager(this)
|
||||
binding.currentTabsRecyclerView.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
|
||||
|
||||
addTabAdapter = TabAdapter(listOf(createTabDataFromId(DIRECT)), true, this)
|
||||
addTabRecyclerView.adapter = addTabAdapter
|
||||
addTabRecyclerView.layoutManager = LinearLayoutManager(this)
|
||||
binding.addTabRecyclerView.adapter = addTabAdapter
|
||||
binding.addTabRecyclerView.layoutManager = LinearLayoutManager(this)
|
||||
|
||||
touchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
|
||||
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
|
||||
|
@ -107,17 +109,17 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
}
|
||||
|
||||
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
|
||||
val temp = currentTabs[viewHolder.adapterPosition]
|
||||
currentTabs[viewHolder.adapterPosition] = currentTabs[target.adapterPosition]
|
||||
currentTabs[target.adapterPosition] = temp
|
||||
val temp = currentTabs[viewHolder.bindingAdapterPosition]
|
||||
currentTabs[viewHolder.bindingAdapterPosition] = currentTabs[target.bindingAdapterPosition]
|
||||
currentTabs[target.bindingAdapterPosition] = temp
|
||||
|
||||
currentTabsAdapter.notifyItemMoved(viewHolder.adapterPosition, target.adapterPosition)
|
||||
currentTabsAdapter.notifyItemMoved(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition)
|
||||
saveTabs()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
onTabRemoved(viewHolder.adapterPosition)
|
||||
onTabRemoved(viewHolder.bindingAdapterPosition)
|
||||
}
|
||||
|
||||
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
||||
|
@ -132,17 +134,17 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
}
|
||||
})
|
||||
|
||||
touchHelper.attachToRecyclerView(currentTabsRecyclerView)
|
||||
touchHelper.attachToRecyclerView(binding.currentTabsRecyclerView)
|
||||
|
||||
actionButton.setOnClickListener {
|
||||
binding.actionButton.setOnClickListener {
|
||||
toggleFab(true)
|
||||
}
|
||||
|
||||
scrim.setOnClickListener {
|
||||
binding.scrim.setOnClickListener {
|
||||
toggleFab(false)
|
||||
}
|
||||
|
||||
maxTabsInfo.text = getString(R.string.max_tab_number_reached, MAX_TAB_COUNT)
|
||||
binding.maxTabsInfo.text = resources.getQuantityString(R.plurals.max_tab_number_reached, MAX_TAB_COUNT, MAX_TAB_COUNT)
|
||||
|
||||
updateAvailableTabs()
|
||||
}
|
||||
|
@ -193,18 +195,18 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
|
||||
private fun toggleFab(expand: Boolean) {
|
||||
val transition = MaterialContainerTransform().apply {
|
||||
startView = if (expand) actionButton else sheet
|
||||
val endView: View = if (expand) sheet else actionButton
|
||||
startView = if (expand) binding.actionButton else binding.sheet
|
||||
val endView: View = if (expand) binding.sheet else binding.actionButton
|
||||
this.endView = endView
|
||||
addTarget(endView)
|
||||
scrimColor = Color.TRANSPARENT
|
||||
setPathMotion(MaterialArcMotion())
|
||||
}
|
||||
|
||||
TransitionManager.beginDelayedTransition(tabPreferenceContainer, transition)
|
||||
actionButton.visible(!expand)
|
||||
sheet.visible(expand)
|
||||
scrim.visible(expand)
|
||||
TransitionManager.beginDelayedTransition(binding.root, transition)
|
||||
binding.actionButton.visible(!expand)
|
||||
binding.sheet.visible(expand)
|
||||
binding.scrim.visible(expand)
|
||||
}
|
||||
|
||||
private fun showAddHashtagDialog(tab: TabData? = null, tabPosition: Int = 0) {
|
||||
|
@ -310,7 +312,7 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
|
||||
addTabAdapter.updateData(addableTabs)
|
||||
|
||||
maxTabsInfo.visible(addableTabs.size == 0 || currentTabs.size >= MAX_TAB_COUNT)
|
||||
binding.maxTabsInfo.visible(addableTabs.size == 0 || currentTabs.size >= MAX_TAB_COUNT)
|
||||
currentTabsAdapter.setRemoveButtonVisible(currentTabs.size > MIN_TAB_COUNT)
|
||||
}
|
||||
|
||||
|
@ -337,7 +339,7 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (actionButton.isVisible) {
|
||||
if (binding.actionButton.isVisible) {
|
||||
super.onBackPressed()
|
||||
} else {
|
||||
toggleFab(false)
|
||||
|
|
|
@ -44,18 +44,19 @@ import androidx.viewpager2.widget.ViewPager2
|
|||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.FutureTarget
|
||||
import com.keylesspalace.tusky.BuildConfig.APPLICATION_ID
|
||||
import com.keylesspalace.tusky.databinding.ActivityViewMediaBinding
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.fragment.ViewImageFragment
|
||||
import com.keylesspalace.tusky.pager.SingleImagePagerAdapter
|
||||
import com.keylesspalace.tusky.pager.ImagePagerAdapter
|
||||
import com.keylesspalace.tusky.util.getTemporaryMediaFilename
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
||||
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider
|
||||
import com.uber.autodispose.autoDispose
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.android.synthetic.main.activity_view_media.*
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.FileOutputStream
|
||||
|
@ -65,27 +66,11 @@ import java.util.*
|
|||
typealias ToolbarVisibilityListener = (isVisible: Boolean) -> Unit
|
||||
|
||||
class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener {
|
||||
companion object {
|
||||
private const val EXTRA_ATTACHMENTS = "attachments"
|
||||
private const val EXTRA_ATTACHMENT_INDEX = "index"
|
||||
private const val EXTRA_SINGLE_IMAGE_URL = "single_image"
|
||||
private const val TAG = "ViewMediaActivity"
|
||||
|
||||
@JvmStatic
|
||||
fun newIntent(context: Context?, attachments: List<AttachmentViewData>, index: Int): Intent {
|
||||
val intent = Intent(context, ViewMediaActivity::class.java)
|
||||
intent.putParcelableArrayListExtra(EXTRA_ATTACHMENTS, ArrayList(attachments))
|
||||
intent.putExtra(EXTRA_ATTACHMENT_INDEX, index)
|
||||
return intent
|
||||
}
|
||||
private val binding by viewBinding(ActivityViewMediaBinding::inflate)
|
||||
|
||||
@JvmStatic
|
||||
fun newSingleImageIntent(context: Context, url: String): Intent {
|
||||
val intent = Intent(context, ViewMediaActivity::class.java)
|
||||
intent.putExtra(EXTRA_SINGLE_IMAGE_URL, url)
|
||||
return intent
|
||||
}
|
||||
}
|
||||
val toolbar: View
|
||||
get() = binding.toolbar
|
||||
|
||||
var isToolbarVisible = true
|
||||
private set
|
||||
|
@ -102,7 +87,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_view_media)
|
||||
setContentView(binding.root)
|
||||
|
||||
supportPostponeEnterTransition()
|
||||
|
||||
|
@ -125,24 +110,24 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
SingleImagePagerAdapter(this, imageUrl!!)
|
||||
}
|
||||
|
||||
viewPager.adapter = adapter
|
||||
viewPager.setCurrentItem(initialPosition, false)
|
||||
viewPager.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
|
||||
binding.viewPager.adapter = adapter
|
||||
binding.viewPager.setCurrentItem(initialPosition, false)
|
||||
binding.viewPager.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
toolbar.title = getPageTitle(position)
|
||||
binding.toolbar.title = getPageTitle(position)
|
||||
}
|
||||
})
|
||||
|
||||
// Setup the toolbar.
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
val actionBar = supportActionBar
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true)
|
||||
actionBar.setDisplayShowHomeEnabled(true)
|
||||
actionBar.title = getPageTitle(initialPosition)
|
||||
}
|
||||
toolbar.setNavigationOnClickListener { supportFinishAfterTransition() }
|
||||
toolbar.setOnMenuItemClickListener { item: MenuItem ->
|
||||
binding.toolbar.setNavigationOnClickListener { supportFinishAfterTransition() }
|
||||
binding.toolbar.setOnMenuItemClickListener { item: MenuItem ->
|
||||
when (item.itemId) {
|
||||
R.id.action_download -> requestDownloadMedia()
|
||||
R.id.action_open_status -> onOpenStatus()
|
||||
|
@ -156,7 +141,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
window.statusBarColor = Color.BLACK
|
||||
window.sharedElementEnterTransition.addListener(object : NoopTransitionListener {
|
||||
override fun onTransitionEnd(transition: Transition) {
|
||||
adapter.onTransitionEnd(viewPager.currentItem)
|
||||
adapter.onTransitionEnd(binding.viewPager.currentItem)
|
||||
window.sharedElementEnterTransition.removeListener(this)
|
||||
}
|
||||
})
|
||||
|
@ -165,7 +150,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.view_media_toolbar, menu)
|
||||
// We don't support 'open status' from single image views
|
||||
menu?.findItem(R.id.action_open_status)?.isVisible = (attachments != null)
|
||||
menu.findItem(R.id.action_open_status)?.isVisible = (attachments != null)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -192,14 +177,14 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
val alpha = if (isToolbarVisible) 1.0f else 0.0f
|
||||
if (isToolbarVisible) {
|
||||
// If to be visible, need to make visible immediately and animate alpha
|
||||
toolbar.alpha = 0.0f
|
||||
toolbar.visibility = visibility
|
||||
binding.toolbar.alpha = 0.0f
|
||||
binding.toolbar.visibility = visibility
|
||||
}
|
||||
|
||||
toolbar.animate().alpha(alpha)
|
||||
binding.toolbar.animate().alpha(alpha)
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
toolbar.visibility = visibility
|
||||
binding.toolbar.visibility = visibility
|
||||
animation.removeListener(this)
|
||||
}
|
||||
})
|
||||
|
@ -214,7 +199,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
}
|
||||
|
||||
private fun downloadMedia() {
|
||||
val url = imageUrl ?: attachments!![viewPager.currentItem].attachment.url
|
||||
val url = imageUrl ?: attachments!![binding.viewPager.currentItem].attachment.url
|
||||
val filename = Uri.parse(url).lastPathSegment
|
||||
Toast.makeText(applicationContext, resources.getString(R.string.download_image, filename), Toast.LENGTH_SHORT).show()
|
||||
|
||||
|
@ -230,18 +215,18 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
downloadMedia()
|
||||
} else {
|
||||
showErrorDialog(toolbar, R.string.error_media_download_permission, R.string.action_retry) { requestDownloadMedia() }
|
||||
showErrorDialog(binding.toolbar, R.string.error_media_download_permission, R.string.action_retry) { requestDownloadMedia() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onOpenStatus() {
|
||||
val attach = attachments!![viewPager.currentItem]
|
||||
val attach = attachments!![binding.viewPager.currentItem]
|
||||
startActivityWithSlideInAnimation(ViewThreadActivity.startIntent(this, attach.statusId, attach.statusUrl))
|
||||
}
|
||||
|
||||
private fun copyLink() {
|
||||
val url = imageUrl ?: attachments!![viewPager.currentItem].attachment.url
|
||||
val url = imageUrl ?: attachments!![binding.viewPager.currentItem].attachment.url
|
||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText(null, url))
|
||||
}
|
||||
|
@ -256,7 +241,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
if (imageUrl != null) {
|
||||
shareImage(directory, imageUrl!!)
|
||||
} else {
|
||||
val attachment = attachments!![viewPager.currentItem].attachment
|
||||
val attachment = attachments!![binding.viewPager.currentItem].attachment
|
||||
when (attachment.type) {
|
||||
Attachment.Type.IMAGE -> shareImage(directory, attachment.url)
|
||||
Attachment.Type.AUDIO,
|
||||
|
@ -280,7 +265,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
|
||||
private fun shareImage(directory: File, url: String) {
|
||||
isCreating = true
|
||||
progressBarShare.visibility = View.VISIBLE
|
||||
binding.progressBarShare.visibility = View.VISIBLE
|
||||
invalidateOptionsMenu()
|
||||
val file = File(directory, getTemporaryMediaFilename("png"))
|
||||
val futureTask: FutureTarget<Bitmap> =
|
||||
|
@ -312,14 +297,14 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
Log.d(TAG, "Download image result: $result")
|
||||
isCreating = false
|
||||
invalidateOptionsMenu()
|
||||
progressBarShare.visibility = View.GONE
|
||||
binding.progressBarShare.visibility = View.GONE
|
||||
if (result)
|
||||
shareFile(file, "image/png")
|
||||
},
|
||||
{ error ->
|
||||
isCreating = false
|
||||
invalidateOptionsMenu()
|
||||
progressBarShare.visibility = View.GONE
|
||||
binding.progressBarShare.visibility = View.GONE
|
||||
Log.e(TAG, "Failed to download image", error)
|
||||
}
|
||||
)
|
||||
|
@ -342,6 +327,28 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
|
||||
shareFile(file, mimeType)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val EXTRA_ATTACHMENTS = "attachments"
|
||||
private const val EXTRA_ATTACHMENT_INDEX = "index"
|
||||
private const val EXTRA_SINGLE_IMAGE_URL = "single_image"
|
||||
private const val TAG = "ViewMediaActivity"
|
||||
|
||||
@JvmStatic
|
||||
fun newIntent(context: Context?, attachments: List<AttachmentViewData>, index: Int): Intent {
|
||||
val intent = Intent(context, ViewMediaActivity::class.java)
|
||||
intent.putParcelableArrayListExtra(EXTRA_ATTACHMENTS, ArrayList(attachments))
|
||||
intent.putExtra(EXTRA_ATTACHMENT_INDEX, index)
|
||||
return intent
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun newSingleImageIntent(context: Context, url: String): Intent {
|
||||
val intent = Intent(context, ViewMediaActivity::class.java)
|
||||
intent.putExtra(EXTRA_SINGLE_IMAGE_URL, url)
|
||||
return intent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ViewMediaAdapter(activity: FragmentActivity): FragmentStateAdapter(activity) {
|
||||
|
|
|
@ -19,60 +19,57 @@ import android.text.method.LinkMovementMethod
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
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 kotlinx.android.synthetic.main.item_account_field.view.*
|
||||
|
||||
class AccountFieldAdapter(private val linkListener: LinkListener, private val animateEmojis: Boolean) : RecyclerView.Adapter<AccountFieldAdapter.ViewHolder>() {
|
||||
class AccountFieldAdapter(
|
||||
private val linkListener: LinkListener,
|
||||
private val animateEmojis: Boolean
|
||||
) : RecyclerView.Adapter<BindingHolder<ItemAccountFieldBinding>>() {
|
||||
|
||||
var emojis: List<Emoji> = emptyList()
|
||||
var fields: List<Either<IdentityProof, Field>> = emptyList()
|
||||
|
||||
override fun getItemCount() = fields.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_account_field, parent, false)
|
||||
return ViewHolder(view)
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemAccountFieldBinding> {
|
||||
val binding = ItemAccountFieldBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return BindingHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemAccountFieldBinding>, position: Int) {
|
||||
val proofOrField = fields[position]
|
||||
val nameTextView = holder.binding.accountFieldName
|
||||
val valueTextView = holder.binding.accountFieldValue
|
||||
|
||||
if(proofOrField.isLeft()) {
|
||||
val identityProof = proofOrField.asLeft()
|
||||
|
||||
viewHolder.nameTextView.text = identityProof.provider
|
||||
viewHolder.valueTextView.text = LinkHelper.createClickableText(identityProof.username, identityProof.profileUrl)
|
||||
nameTextView.text = identityProof.provider
|
||||
valueTextView.text = LinkHelper.createClickableText(identityProof.username, identityProof.profileUrl)
|
||||
|
||||
viewHolder.valueTextView.movementMethod = LinkMovementMethod.getInstance()
|
||||
valueTextView.movementMethod = LinkMovementMethod.getInstance()
|
||||
|
||||
viewHolder.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, viewHolder.nameTextView, animateEmojis)
|
||||
viewHolder.nameTextView.text = emojifiedName
|
||||
val emojifiedName = field.name.emojify(emojis, nameTextView, animateEmojis)
|
||||
nameTextView.text = emojifiedName
|
||||
|
||||
val emojifiedValue = field.value.emojify(emojis, viewHolder.valueTextView, animateEmojis)
|
||||
LinkHelper.setClickableText(viewHolder.valueTextView, emojifiedValue, null, linkListener)
|
||||
val emojifiedValue = field.value.emojify(emojis, valueTextView, animateEmojis)
|
||||
LinkHelper.setClickableText(valueTextView, emojifiedValue, null, linkListener)
|
||||
|
||||
if(field.verifiedAt != null) {
|
||||
viewHolder.valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
|
||||
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
|
||||
} else {
|
||||
viewHolder.valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0 )
|
||||
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0 )
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ViewHolder(rootView: View) : RecyclerView.ViewHolder(rootView) {
|
||||
val nameTextView: TextView = rootView.accountFieldName
|
||||
val valueTextView: TextView = rootView.accountFieldValue
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,18 +15,16 @@
|
|||
|
||||
package com.keylesspalace.tusky.adapter
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import com.keylesspalace.tusky.R
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.databinding.ItemEditFieldBinding
|
||||
import com.keylesspalace.tusky.entity.StringField
|
||||
import kotlinx.android.synthetic.main.item_edit_field.view.*
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
|
||||
class AccountFieldEditAdapter : RecyclerView.Adapter<AccountFieldEditAdapter.ViewHolder>() {
|
||||
class AccountFieldEditAdapter : RecyclerView.Adapter<BindingHolder<ItemEditFieldBinding>>() {
|
||||
|
||||
private val fieldData = mutableListOf<MutableStringPair>()
|
||||
|
||||
|
@ -54,20 +52,20 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<AccountFieldEditAdapter.Vie
|
|||
notifyItemInserted(fieldData.size - 1)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = fieldData.size
|
||||
override fun getItemCount() = fieldData.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_edit_field, parent, false)
|
||||
return ViewHolder(view)
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemEditFieldBinding> {
|
||||
val binding = ItemEditFieldBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return BindingHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
|
||||
viewHolder.nameTextView.setText(fieldData[position].first)
|
||||
viewHolder.valueTextView.setText(fieldData[position].second)
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemEditFieldBinding>, position: Int) {
|
||||
holder.binding.accountFieldName.setText(fieldData[position].first)
|
||||
holder.binding.accountFieldValue.setText(fieldData[position].second)
|
||||
|
||||
viewHolder.nameTextView.addTextChangedListener(object: TextWatcher {
|
||||
holder.binding.accountFieldName.addTextChangedListener(object: TextWatcher {
|
||||
override fun afterTextChanged(newText: Editable) {
|
||||
fieldData[viewHolder.adapterPosition].first = newText.toString()
|
||||
fieldData[holder.bindingAdapterPosition].first = newText.toString()
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
|
@ -75,9 +73,9 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<AccountFieldEditAdapter.Vie
|
|||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||
})
|
||||
|
||||
viewHolder.valueTextView.addTextChangedListener(object: TextWatcher {
|
||||
holder.binding.accountFieldValue.addTextChangedListener(object: TextWatcher {
|
||||
override fun afterTextChanged(newText: Editable) {
|
||||
fieldData[viewHolder.adapterPosition].second = newText.toString()
|
||||
fieldData[holder.bindingAdapterPosition].second = newText.toString()
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
|
@ -87,12 +85,6 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<AccountFieldEditAdapter.Vie
|
|||
|
||||
}
|
||||
|
||||
class ViewHolder(rootView: View) : RecyclerView.ViewHolder(rootView) {
|
||||
val nameTextView: EditText = rootView.accountFieldName
|
||||
val valueTextView: EditText = rootView.accountFieldValue
|
||||
}
|
||||
|
||||
class MutableStringPair (var first: String, var second: String)
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -22,39 +22,35 @@ import android.view.ViewGroup
|
|||
import android.widget.ArrayAdapter
|
||||
import androidx.preference.PreferenceManager
|
||||
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 kotlinx.android.synthetic.main.item_autocomplete_account.view.*
|
||||
|
||||
class AccountSelectionAdapter(context: Context) : ArrayAdapter<AccountEntity>(context, R.layout.item_autocomplete_account) {
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
var view = convertView
|
||||
|
||||
if (convertView == null) {
|
||||
val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
view = layoutInflater.inflate(R.layout.item_autocomplete_account, parent, false)
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val binding = if (convertView == null) {
|
||||
ItemAutocompleteAccountBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||
} else {
|
||||
ItemAutocompleteAccountBinding.bind(convertView)
|
||||
}
|
||||
view!!
|
||||
|
||||
val account = getItem(position)
|
||||
if (account != null) {
|
||||
val username = view.username
|
||||
val displayName = view.display_name
|
||||
val avatar = view.avatar
|
||||
val pm = PreferenceManager.getDefaultSharedPreferences(avatar.context)
|
||||
val pm = PreferenceManager.getDefaultSharedPreferences(binding.avatar.context)
|
||||
val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||
|
||||
username.text = account.fullName
|
||||
displayName.text = account.displayName.emojify(account.emojis, displayName, animateEmojis)
|
||||
binding.username.text = account.fullName
|
||||
binding.displayName.text = account.displayName.emojify(account.emojis, binding.displayName, animateEmojis)
|
||||
|
||||
val avatarRadius = avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_42dp)
|
||||
val avatarRadius = context.resources.getDimensionPixelSize(R.dimen.avatar_radius_42dp)
|
||||
val animateAvatar = pm.getBoolean("animateGifAvatars", false)
|
||||
|
||||
loadAvatar(account.profilePictureUrl, avatar, avatarRadius, animateAvatar)
|
||||
loadAvatar(account.profilePictureUrl, binding.avatar, avatarRadius, animateAvatar)
|
||||
|
||||
}
|
||||
|
||||
return view
|
||||
return binding.root
|
||||
}
|
||||
}
|
|
@ -95,7 +95,7 @@ public class BlocksAdapter extends AccountAdapter {
|
|||
|
||||
void setupActionListener(final AccountActionListener listener) {
|
||||
unblock.setOnClickListener(v -> {
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onBlock(false, id, position);
|
||||
}
|
||||
|
|
|
@ -15,48 +15,44 @@
|
|||
|
||||
package com.keylesspalace.tusky.adapter
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.ItemEmojiButtonBinding
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
import java.util.*
|
||||
|
||||
class EmojiAdapter(emojiList: List<Emoji>, private val onEmojiSelectedListener: OnEmojiSelectedListener) : RecyclerView.Adapter<EmojiAdapter.EmojiHolder>() {
|
||||
private val emojiList : List<Emoji>
|
||||
class EmojiAdapter(
|
||||
emojiList: List<Emoji>,
|
||||
private val onEmojiSelectedListener: OnEmojiSelectedListener
|
||||
) : RecyclerView.Adapter<BindingHolder<ItemEmojiButtonBinding>>() {
|
||||
|
||||
init {
|
||||
this.emojiList = emojiList.filter { emoji -> emoji.visibleInPicker == null || emoji.visibleInPicker }
|
||||
.sortedBy { it.shortcode.toLowerCase(Locale.ROOT) }
|
||||
private val emojiList : List<Emoji> = emojiList.filter { emoji -> emoji.visibleInPicker == null || emoji.visibleInPicker }
|
||||
.sortedBy { it.shortcode.toLowerCase(Locale.ROOT) }
|
||||
|
||||
override fun getItemCount() = emojiList.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemEmojiButtonBinding> {
|
||||
val binding = ItemEmojiButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return BindingHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return emojiList.size
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmojiHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_emoji_button, parent, false) as ImageView
|
||||
return EmojiHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(viewHolder: EmojiHolder, position: Int) {
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemEmojiButtonBinding>, position: Int) {
|
||||
val emoji = emojiList[position]
|
||||
val emojiImageView = holder.binding.root
|
||||
|
||||
Glide.with(viewHolder.emojiImageView)
|
||||
Glide.with(emojiImageView)
|
||||
.load(emoji.url)
|
||||
.into(viewHolder.emojiImageView)
|
||||
.into(emojiImageView)
|
||||
|
||||
viewHolder.emojiImageView.setOnClickListener {
|
||||
emojiImageView.setOnClickListener {
|
||||
onEmojiSelectedListener.onEmojiSelected(emoji.shortcode)
|
||||
}
|
||||
|
||||
viewHolder.emojiImageView.contentDescription = emoji.shortcode
|
||||
emojiImageView.contentDescription = emoji.shortcode
|
||||
}
|
||||
|
||||
class EmojiHolder(val emojiImageView: ImageView) : RecyclerView.ViewHolder(emojiImageView)
|
||||
|
||||
}
|
||||
|
||||
interface OnEmojiSelectedListener {
|
||||
|
|
|
@ -1,55 +1,67 @@
|
|||
/* Copyright 2021 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.adapter
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import android.text.style.StyleSpan
|
||||
import android.view.View
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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 kotlinx.android.synthetic.main.item_follow_request_notification.view.*
|
||||
|
||||
internal class FollowRequestViewHolder(
|
||||
itemView: View,
|
||||
private val showHeader: Boolean) : RecyclerView.ViewHolder(itemView) {
|
||||
private var id: String? = null
|
||||
class FollowRequestViewHolder(
|
||||
private val binding: ItemFollowRequestBinding,
|
||||
private val showHeader: Boolean
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun setupWithAccount(account: Account, animateAvatar: Boolean, animateEmojis: Boolean) {
|
||||
id = account.id
|
||||
val wrappedName = account.name.unicodeWrap()
|
||||
val emojifiedName: CharSequence = wrappedName.emojify(account.emojis, itemView, animateEmojis)
|
||||
itemView.displayNameTextView.text = emojifiedName
|
||||
binding.displayNameTextView.text = emojifiedName
|
||||
if (showHeader) {
|
||||
val wholeMessage: String = itemView.context.getString(R.string.notification_follow_request_format, wrappedName)
|
||||
itemView.notificationTextView?.text = SpannableStringBuilder(wholeMessage).apply {
|
||||
binding.notificationTextView.text = SpannableStringBuilder(wholeMessage).apply {
|
||||
setSpan(StyleSpan(Typeface.BOLD), 0, wrappedName.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}.emojify(account.emojis, itemView, animateEmojis)
|
||||
}
|
||||
itemView.notificationTextView?.visible(showHeader)
|
||||
binding.notificationTextView.visible(showHeader)
|
||||
val format = itemView.context.getString(R.string.status_username_format)
|
||||
val formattedUsername = String.format(format, account.username)
|
||||
itemView.usernameTextView.text = formattedUsername
|
||||
val avatarRadius = itemView.avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp)
|
||||
loadAvatar(account.avatar, itemView.avatar, avatarRadius, animateAvatar)
|
||||
binding.usernameTextView.text = formattedUsername
|
||||
val avatarRadius = binding.avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp)
|
||||
loadAvatar(account.avatar, binding.avatar, avatarRadius, animateAvatar)
|
||||
}
|
||||
|
||||
fun setupActionListener(listener: AccountActionListener) {
|
||||
itemView.acceptButton.setOnClickListener {
|
||||
val position = adapterPosition
|
||||
fun setupActionListener(listener: AccountActionListener, accountId: String) {
|
||||
binding.acceptButton.setOnClickListener {
|
||||
val position = bindingAdapterPosition
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onRespondToFollowRequest(true, id, position)
|
||||
listener.onRespondToFollowRequest(true, accountId, position)
|
||||
}
|
||||
}
|
||||
itemView.rejectButton.setOnClickListener {
|
||||
val position = adapterPosition
|
||||
binding.rejectButton.setOnClickListener {
|
||||
val position = bindingAdapterPosition
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onRespondToFollowRequest(false, id, position)
|
||||
listener.onRespondToFollowRequest(false, accountId, position)
|
||||
}
|
||||
}
|
||||
itemView.setOnClickListener { listener.onViewAccount(id) }
|
||||
itemView.setOnClickListener { listener.onViewAccount(accountId) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import androidx.annotation.NonNull;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding;
|
||||
import com.keylesspalace.tusky.interfaces.AccountActionListener;
|
||||
|
||||
public class FollowRequestsAdapter extends AccountAdapter {
|
||||
|
@ -37,9 +38,8 @@ public class FollowRequestsAdapter extends AccountAdapter {
|
|||
switch (viewType) {
|
||||
default:
|
||||
case VIEW_TYPE_ACCOUNT: {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_follow_request, parent, false);
|
||||
return new FollowRequestViewHolder(view, false);
|
||||
ItemFollowRequestBinding binding = ItemFollowRequestBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
|
||||
return new FollowRequestViewHolder(binding, false);
|
||||
}
|
||||
case VIEW_TYPE_FOOTER: {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
|
@ -54,7 +54,7 @@ public class FollowRequestsAdapter extends AccountAdapter {
|
|||
if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) {
|
||||
FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
|
||||
holder.setupWithAccount(accountList.get(position), animateAvatar, animateEmojis);
|
||||
holder.setupActionListener(accountActionListener);
|
||||
holder.setupActionListener(accountActionListener, accountList.get(position).getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/* Copyright 2020 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.adapter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
|
||||
class FollowRequestsHeaderAdapter(private val instanceName: String, private val accountLocked: Boolean) : RecyclerView.Adapter<HeaderViewHolder>() {
|
||||
|
||||
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
|
||||
return HeaderViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(viewHolder: HeaderViewHolder, position: Int) {
|
||||
viewHolder.textView.text = viewHolder.textView.context.getString(R.string.follow_requests_info, instanceName)
|
||||
}
|
||||
|
||||
override fun getItemCount() = if (accountLocked) 0 else 1
|
||||
|
||||
}
|
||||
|
||||
class HeaderViewHolder(var textView: TextView) : RecyclerView.ViewHolder(textView)
|
|
@ -1,16 +0,0 @@
|
|||
package com.keylesspalace.tusky.adapter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
|
||||
class HashtagViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val hashtag: TextView = itemView.findViewById(R.id.hashtag)
|
||||
|
||||
fun setup(tag: String, listener: LinkListener) {
|
||||
hashtag.text = String.format("#%s", tag)
|
||||
hashtag.setOnClickListener { listener.onViewTag(tag) }
|
||||
}
|
||||
}
|
|
@ -21,21 +21,22 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.ItemPickerListBinding
|
||||
import com.keylesspalace.tusky.entity.MastoList
|
||||
import kotlinx.android.synthetic.main.item_picker_list.view.*
|
||||
|
||||
class ListSelectionAdapter(context: Context) : ArrayAdapter<MastoList>(context, R.layout.item_autocomplete_hashtag) {
|
||||
class ListSelectionAdapter(context: Context) : ArrayAdapter<MastoList>(context, R.layout.item_picker_list) {
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
|
||||
val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
|
||||
val view = convertView
|
||||
?: layoutInflater.inflate(R.layout.item_picker_list, parent, false)
|
||||
|
||||
getItem(position)?.let { list ->
|
||||
view.title.text = list.title
|
||||
val binding = if (convertView == null) {
|
||||
ItemPickerListBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||
} else {
|
||||
ItemPickerListBinding.bind(convertView)
|
||||
}
|
||||
|
||||
return view
|
||||
getItem(position)?.let { list ->
|
||||
binding.root.text = list.title
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import android.widget.TextView;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
|
@ -123,9 +122,9 @@ public class MutesAdapter extends AccountAdapter {
|
|||
}
|
||||
|
||||
void setupActionListener(final AccountActionListener listener) {
|
||||
unmute.setOnClickListener(v -> listener.onMute(false, id, getAdapterPosition(), false));
|
||||
unmute.setOnClickListener(v -> listener.onMute(false, id, getBindingAdapterPosition(), false));
|
||||
muteNotifications.setOnClickListener(
|
||||
v -> listener.onMute(true, id, getAdapterPosition(), !notifications));
|
||||
v -> listener.onMute(true, id, getBindingAdapterPosition(), !notifications));
|
||||
itemView.setOnClickListener(v -> listener.onViewAccount(id));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,29 +16,28 @@
|
|||
package com.keylesspalace.tusky.adapter
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.keylesspalace.tusky.databinding.ItemNetworkStateBinding
|
||||
import com.keylesspalace.tusky.util.NetworkState
|
||||
import com.keylesspalace.tusky.util.Status
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
import kotlinx.android.synthetic.main.item_network_state.view.*
|
||||
|
||||
class NetworkStateViewHolder(itemView: View,
|
||||
class NetworkStateViewHolder(private val binding: ItemNetworkStateBinding,
|
||||
private val retryCallback: () -> Unit)
|
||||
: RecyclerView.ViewHolder(itemView) {
|
||||
: RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun setUpWithNetworkState(state: NetworkState?, fullScreen: Boolean) {
|
||||
itemView.progressBar.visible(state?.status == Status.RUNNING)
|
||||
itemView.retryButton.visible(state?.status == Status.FAILED)
|
||||
itemView.errorMsg.visible(state?.msg != null)
|
||||
itemView.errorMsg.text = state?.msg
|
||||
itemView.retryButton.setOnClickListener {
|
||||
binding.progressBar.visible(state?.status == Status.RUNNING)
|
||||
binding.retryButton.visible(state?.status == Status.FAILED)
|
||||
binding.errorMsg.visible(state?.msg != null)
|
||||
binding.errorMsg.text = state?.msg
|
||||
binding.retryButton.setOnClickListener {
|
||||
retryCallback()
|
||||
}
|
||||
if(fullScreen) {
|
||||
itemView.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.root.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
} else {
|
||||
itemView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
binding.root.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding;
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.entity.Emoji;
|
||||
import com.keylesspalace.tusky.entity.Notification;
|
||||
|
@ -125,9 +126,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
|||
return new FollowViewHolder(view, statusDisplayOptions);
|
||||
}
|
||||
case VIEW_TYPE_FOLLOW_REQUEST: {
|
||||
View view = inflater
|
||||
.inflate(R.layout.item_follow_request_notification, parent, false);
|
||||
return new FollowRequestViewHolder(view, true);
|
||||
ItemFollowRequestBinding binding = ItemFollowRequestBinding.inflate(inflater, parent, false);
|
||||
return new FollowRequestViewHolder(binding, true);
|
||||
}
|
||||
case VIEW_TYPE_PLACEHOLDER: {
|
||||
View view = inflater
|
||||
|
@ -233,8 +233,9 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
|||
if (payloadForHolder == null) {
|
||||
FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
|
||||
holder.setupWithAccount(concreteNotificaton.getAccount(), statusDisplayOptions.animateAvatars(), statusDisplayOptions.animateEmojis());
|
||||
holder.setupActionListener(accountActionListener);
|
||||
holder.setupActionListener(accountActionListener, concreteNotificaton.getAccount().getId());
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
@ -540,8 +541,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
|||
}
|
||||
|
||||
contentWarningButton.setOnClickListener(view -> {
|
||||
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
notificationActionListener.onExpandedChange(!statusViewData.isExpanded(), getAdapterPosition());
|
||||
if (getBindingAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
notificationActionListener.onExpandedChange(!statusViewData.isExpanded(), getBindingAdapterPosition());
|
||||
}
|
||||
statusContent.setVisibility(statusViewData.isExpanded() ? View.GONE : View.VISIBLE);
|
||||
});
|
||||
|
@ -618,7 +619,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
|||
|
||||
if (statusViewData.isCollapsible() && (statusViewData.isExpanded() || !hasSpoiler)) {
|
||||
contentCollapseButton.setOnClickListener(view -> {
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION && notificationActionListener != null) {
|
||||
notificationActionListener.onNotificationContentCollapsedChange(!statusViewData.isCollapsed(), position);
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public final class PlaceholderViewHolder extends RecyclerView.ViewHolder {
|
|||
loadMoreButton.setEnabled(true);
|
||||
loadMoreButton.setOnClickListener(v -> {
|
||||
loadMoreButton.setEnabled(false);
|
||||
listener.onLoadMore(getAdapterPosition());
|
||||
listener.onLoadMore(getBindingAdapterPosition());
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
@ -18,19 +18,18 @@ package com.keylesspalace.tusky.adapter
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CheckBox
|
||||
import android.widget.RadioButton
|
||||
import android.widget.TextView
|
||||
import androidx.emoji.text.EmojiCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.ItemPollBinding
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
import com.keylesspalace.tusky.viewdata.PollOptionViewData
|
||||
import com.keylesspalace.tusky.viewdata.buildDescription
|
||||
import com.keylesspalace.tusky.viewdata.calculatePercent
|
||||
|
||||
class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
|
||||
class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||
|
||||
private var pollOptions: List<PollOptionViewData> = emptyList()
|
||||
private var voteCount: Int = 0
|
||||
|
@ -64,51 +63,54 @@ class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
|
|||
}
|
||||
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PollViewHolder {
|
||||
return PollViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_poll, parent, false))
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemPollBinding> {
|
||||
val binding = ItemPollBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return BindingHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return pollOptions.size
|
||||
}
|
||||
override fun getItemCount() = pollOptions.size
|
||||
|
||||
override fun onBindViewHolder(holder: PollViewHolder, position: Int) {
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemPollBinding>, position: Int) {
|
||||
|
||||
val option = pollOptions[position]
|
||||
|
||||
holder.resultTextView.visible(mode == RESULT)
|
||||
holder.radioButton.visible(mode == SINGLE)
|
||||
holder.checkBox.visible(mode == MULTIPLE)
|
||||
val resultTextView = holder.binding.statusPollOptionResult
|
||||
val radioButton = holder.binding.statusPollRadioButton
|
||||
val checkBox = holder.binding.statusPollCheckbox
|
||||
|
||||
resultTextView.visible(mode == RESULT)
|
||||
radioButton.visible(mode == SINGLE)
|
||||
checkBox.visible(mode == MULTIPLE)
|
||||
|
||||
when(mode) {
|
||||
RESULT -> {
|
||||
val percent = calculatePercent(option.votesCount, votersCount, voteCount)
|
||||
val emojifiedPollOptionText = buildDescription(option.title, percent, holder.resultTextView.context)
|
||||
.emojify(emojis, holder.resultTextView, animateEmojis)
|
||||
holder.resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText)
|
||||
val emojifiedPollOptionText = buildDescription(option.title, percent, resultTextView.context)
|
||||
.emojify(emojis, resultTextView, animateEmojis)
|
||||
resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText)
|
||||
|
||||
val level = percent * 100
|
||||
|
||||
holder.resultTextView.background.level = level
|
||||
holder.resultTextView.setOnClickListener(resultClickListener)
|
||||
resultTextView.background.level = level
|
||||
resultTextView.setOnClickListener(resultClickListener)
|
||||
}
|
||||
SINGLE -> {
|
||||
val emojifiedPollOptionText = option.title.emojify(emojis, holder.radioButton, animateEmojis)
|
||||
holder.radioButton.text = EmojiCompat.get().process(emojifiedPollOptionText)
|
||||
holder.radioButton.isChecked = option.selected
|
||||
holder.radioButton.setOnClickListener {
|
||||
val emojifiedPollOptionText = option.title.emojify(emojis, radioButton, animateEmojis)
|
||||
radioButton.text = EmojiCompat.get().process(emojifiedPollOptionText)
|
||||
radioButton.isChecked = option.selected
|
||||
radioButton.setOnClickListener {
|
||||
pollOptions.forEachIndexed { index, pollOption ->
|
||||
pollOption.selected = index == holder.adapterPosition
|
||||
pollOption.selected = index == holder.bindingAdapterPosition
|
||||
notifyItemChanged(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
MULTIPLE -> {
|
||||
val emojifiedPollOptionText = option.title.emojify(emojis, holder.checkBox, animateEmojis)
|
||||
holder.checkBox.text = EmojiCompat.get().process(emojifiedPollOptionText)
|
||||
holder.checkBox.isChecked = option.selected
|
||||
holder.checkBox.setOnCheckedChangeListener { _, isChecked ->
|
||||
pollOptions[holder.adapterPosition].selected = isChecked
|
||||
val emojifiedPollOptionText = option.title.emojify(emojis, checkBox, animateEmojis)
|
||||
checkBox.text = EmojiCompat.get().process(emojifiedPollOptionText)
|
||||
checkBox.isChecked = option.selected
|
||||
checkBox.setOnCheckedChangeListener { _, isChecked ->
|
||||
pollOptions[holder.bindingAdapterPosition].selected = isChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,13 +123,3 @@ class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
|
|||
const val MULTIPLE = 2
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class PollViewHolder(view: View): RecyclerView.ViewHolder(view) {
|
||||
|
||||
val resultTextView: TextView = view.findViewById(R.id.status_poll_option_result)
|
||||
val radioButton: RadioButton = view.findViewById(R.id.status_poll_radio_button)
|
||||
val checkBox: CheckBox = view.findViewById(R.id.status_poll_checkbox)
|
||||
|
||||
}
|
||||
|
|
|
@ -63,5 +63,4 @@ class PreviewPollOptionsAdapter: RecyclerView.Adapter<PreviewViewHolder>() {
|
|||
|
||||
}
|
||||
|
||||
|
||||
class PreviewViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
|
||||
class PreviewViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
|
||||
|
|
|
@ -113,9 +113,9 @@ public class SavedTootAdapter extends RecyclerView.Adapter {
|
|||
|
||||
suppr.setOnClickListener(v -> {
|
||||
v.setEnabled(false);
|
||||
handler.delete(getAdapterPosition(), item);
|
||||
handler.delete(getBindingAdapterPosition(), item);
|
||||
});
|
||||
view.setOnClickListener(v -> handler.click(getAdapterPosition(), item));
|
||||
view.setOnClickListener(v -> handler.click(getBindingAdapterPosition(), item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -217,8 +217,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
setContentWarningButtonText(expanded);
|
||||
contentWarningButton.setOnClickListener(view -> {
|
||||
contentWarningDescription.invalidate();
|
||||
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
listener.onExpandedChange(!expanded, getAdapterPosition());
|
||||
if (getBindingAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
listener.onExpandedChange(!expanded, getBindingAdapterPosition());
|
||||
}
|
||||
setContentWarningButtonText(!expanded);
|
||||
|
||||
|
@ -513,15 +513,15 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
sensitiveMediaWarning.setVisibility(showingContent ? View.GONE : View.VISIBLE);
|
||||
sensitiveMediaShow.setVisibility(showingContent ? View.VISIBLE : View.GONE);
|
||||
sensitiveMediaShow.setOnClickListener(v -> {
|
||||
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
listener.onContentHiddenChange(false, getAdapterPosition());
|
||||
if (getBindingAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
listener.onContentHiddenChange(false, getBindingAdapterPosition());
|
||||
}
|
||||
v.setVisibility(View.GONE);
|
||||
sensitiveMediaWarning.setVisibility(View.VISIBLE);
|
||||
});
|
||||
sensitiveMediaWarning.setOnClickListener(v -> {
|
||||
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
listener.onContentHiddenChange(true, getAdapterPosition());
|
||||
if (getBindingAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
listener.onContentHiddenChange(true, getBindingAdapterPosition());
|
||||
}
|
||||
v.setVisibility(View.GONE);
|
||||
sensitiveMediaShow.setVisibility(View.VISIBLE);
|
||||
|
@ -582,10 +582,10 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
private void setAttachmentClickListener(View view, StatusActionListener listener,
|
||||
int index, Attachment attachment, boolean animateTransition) {
|
||||
view.setOnClickListener(v -> {
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
if (sensitiveMediaWarning.getVisibility() == View.VISIBLE) {
|
||||
listener.onContentHiddenChange(true, getAdapterPosition());
|
||||
listener.onContentHiddenChange(true, getBindingAdapterPosition());
|
||||
} else {
|
||||
listener.onViewMedia(position, index, animateTransition ? v : null);
|
||||
}
|
||||
|
@ -627,7 +627,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
displayName.setOnClickListener(profileButtonClickListener);
|
||||
|
||||
replyButton.setOnClickListener(v -> {
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onReply(position);
|
||||
}
|
||||
|
@ -635,7 +635,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
if (reblogButton != null) {
|
||||
reblogButton.setEventListener((button, buttonState) -> {
|
||||
// return true to play animaion
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
if (statusDisplayOptions.confirmReblogs()) {
|
||||
showConfirmReblogDialog(listener, statusContent, buttonState, position);
|
||||
|
@ -651,7 +651,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
|
||||
favouriteButton.setEventListener((button, buttonState) -> {
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onFavourite(!buttonState, position);
|
||||
}
|
||||
|
@ -659,7 +659,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
});
|
||||
|
||||
bookmarkButton.setEventListener((button, buttonState) -> {
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onBookmark(!buttonState, position);
|
||||
}
|
||||
|
@ -667,7 +667,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
});
|
||||
|
||||
moreButton.setOnClickListener(v -> {
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onMore(v, position);
|
||||
}
|
||||
|
@ -677,7 +677,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
* just eat the clicks instead of deferring to the parent listener, but WILL respond to a
|
||||
* listener directly on the TextView, for whatever reason. */
|
||||
View.OnClickListener viewThreadListener = v -> {
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onViewThread(position);
|
||||
}
|
||||
|
@ -926,7 +926,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
if (expired || poll.getVoted()) {
|
||||
// no voting possible
|
||||
View.OnClickListener viewThreadListener = v -> {
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onViewThread(position);
|
||||
}
|
||||
|
@ -958,7 +958,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
|
||||
pollButton.setOnClickListener(v -> {
|
||||
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
|
||||
|
|
|
@ -72,13 +72,13 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
|||
}
|
||||
|
||||
reblogs.setOnClickListener(v -> {
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onShowReblogs(position);
|
||||
}
|
||||
});
|
||||
favourites.setOnClickListener(v -> {
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onShowFavs(position);
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ public class StatusViewHolder extends StatusBaseViewHolder {
|
|||
hideStatusInfo();
|
||||
} else {
|
||||
setRebloggedByDisplayName(rebloggedByDisplayName, status, statusDisplayOptions);
|
||||
statusInfo.setOnClickListener(v -> listener.onOpenReblog(getAdapterPosition()));
|
||||
statusInfo.setOnClickListener(v -> listener.onOpenReblog(getBindingAdapterPosition()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ public class StatusViewHolder extends StatusBaseViewHolder {
|
|||
/* input filter for TextViews have to be set before text */
|
||||
if (status.isCollapsible() && (status.isExpanded() || TextUtils.isEmpty(status.getSpoilerText()))) {
|
||||
contentCollapseButton.setOnClickListener(view -> {
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION)
|
||||
listener.onContentCollapsedChange(!status.isCollapsed(), position);
|
||||
});
|
||||
|
|
|
@ -18,19 +18,21 @@ package com.keylesspalace.tusky.adapter
|
|||
import android.content.res.ColorStateList
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.size
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.keylesspalace.tusky.HASHTAG
|
||||
import com.keylesspalace.tusky.LIST
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.TabData
|
||||
import com.keylesspalace.tusky.databinding.ItemTabPreferenceBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemTabPreferenceSmallBinding
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import kotlinx.android.synthetic.main.item_tab_preference.view.*
|
||||
|
||||
interface ItemInteractionListener {
|
||||
fun onTabAdded(tab: TabData)
|
||||
|
@ -44,61 +46,69 @@ interface ItemInteractionListener {
|
|||
class TabAdapter(private var data: List<TabData>,
|
||||
private val small: Boolean,
|
||||
private val listener: ItemInteractionListener,
|
||||
private var removeButtonEnabled: Boolean = false) : RecyclerView.Adapter<TabAdapter.ViewHolder>() {
|
||||
private var removeButtonEnabled: Boolean = false
|
||||
) : RecyclerView.Adapter<BindingHolder<ViewBinding>>() {
|
||||
|
||||
fun updateData(newData: List<TabData>) {
|
||||
this.data = newData
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val layoutId = if (small) {
|
||||
R.layout.item_tab_preference_small
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ViewBinding> {
|
||||
val binding = if (small) {
|
||||
ItemTabPreferenceSmallBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
} else {
|
||||
R.layout.item_tab_preference
|
||||
ItemTabPreferenceBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
}
|
||||
val view = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
|
||||
return ViewHolder(view)
|
||||
return BindingHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
override fun onBindViewHolder(holder: BindingHolder<ViewBinding>, position: Int) {
|
||||
val context = holder.itemView.context
|
||||
val tab = data[position]
|
||||
if (!small && tab.id == LIST) {
|
||||
holder.itemView.textView.text = tab.arguments.getOrNull(1).orEmpty()
|
||||
} else {
|
||||
holder.itemView.textView.setText(tab.text)
|
||||
}
|
||||
holder.itemView.textView.setCompoundDrawablesRelativeWithIntrinsicBounds(tab.icon, 0, 0, 0)
|
||||
|
||||
if (small) {
|
||||
holder.itemView.textView.setOnClickListener {
|
||||
val binding = holder.binding as ItemTabPreferenceSmallBinding
|
||||
|
||||
binding.textView.setText(tab.text)
|
||||
|
||||
binding.textView.setCompoundDrawablesRelativeWithIntrinsicBounds(tab.icon, 0, 0, 0)
|
||||
|
||||
binding.textView.setOnClickListener {
|
||||
listener.onTabAdded(tab)
|
||||
}
|
||||
}
|
||||
holder.itemView.imageView?.setOnTouchListener { _, event ->
|
||||
if (event.action == MotionEvent.ACTION_DOWN) {
|
||||
listener.onStartDrag(holder)
|
||||
true
|
||||
|
||||
} else {
|
||||
val binding = holder.binding as ItemTabPreferenceBinding
|
||||
|
||||
if (tab.id == LIST) {
|
||||
binding.textView.text = tab.arguments.getOrNull(1).orEmpty()
|
||||
} else {
|
||||
false
|
||||
binding.textView.setText(tab.text)
|
||||
}
|
||||
}
|
||||
holder.itemView.removeButton?.setOnClickListener {
|
||||
listener.onTabRemoved(holder.adapterPosition)
|
||||
}
|
||||
if (holder.itemView.removeButton != null) {
|
||||
holder.itemView.removeButton.isEnabled = removeButtonEnabled
|
||||
|
||||
binding.textView.setCompoundDrawablesRelativeWithIntrinsicBounds(tab.icon, 0, 0, 0)
|
||||
|
||||
binding.imageView.setOnTouchListener { _, event ->
|
||||
if (event.action == MotionEvent.ACTION_DOWN) {
|
||||
listener.onStartDrag(holder)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
binding.removeButton.setOnClickListener {
|
||||
listener.onTabRemoved(holder.bindingAdapterPosition)
|
||||
}
|
||||
binding.removeButton.isEnabled = removeButtonEnabled
|
||||
ThemeUtils.setDrawableTint(
|
||||
holder.itemView.context,
|
||||
holder.itemView.removeButton.drawable,
|
||||
binding.removeButton.drawable,
|
||||
(if (removeButtonEnabled) android.R.attr.textColorTertiary else R.attr.textColorDisabled)
|
||||
)
|
||||
}
|
||||
|
||||
if (!small) {
|
||||
|
||||
if (tab.id == HASHTAG) {
|
||||
holder.itemView.chipGroup.show()
|
||||
binding.chipGroup.show()
|
||||
|
||||
/*
|
||||
* The chip group will always contain the actionChip (it is defined in the xml layout).
|
||||
|
@ -107,9 +117,9 @@ class TabAdapter(private var data: List<TabData>,
|
|||
*/
|
||||
tab.arguments.forEachIndexed { i, arg ->
|
||||
|
||||
val chip = holder.itemView.chipGroup.getChildAt(i).takeUnless { it.id == R.id.actionChip } as Chip?
|
||||
val chip = binding.chipGroup.getChildAt(i).takeUnless { it.id == R.id.actionChip } as Chip?
|
||||
?: Chip(context).apply {
|
||||
holder.itemView.chipGroup.addView(this, holder.itemView.chipGroup.size - 1)
|
||||
binding.chipGroup.addView(this, binding.chipGroup.size - 1)
|
||||
chipIconTint = ColorStateList.valueOf(ThemeUtils.getColor(context, android.R.attr.textColorPrimary))
|
||||
}
|
||||
|
||||
|
@ -121,21 +131,21 @@ class TabAdapter(private var data: List<TabData>,
|
|||
} else {
|
||||
chip.setChipIconResource(R.drawable.ic_cancel_24dp)
|
||||
chip.setOnClickListener {
|
||||
listener.onChipClicked(tab, holder.adapterPosition, i)
|
||||
listener.onChipClicked(tab, holder.bindingAdapterPosition, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while(holder.itemView.chipGroup.size - 1 > tab.arguments.size) {
|
||||
holder.itemView.chipGroup.removeViewAt(tab.arguments.size)
|
||||
while(binding.chipGroup.size - 1 > tab.arguments.size) {
|
||||
binding.chipGroup.removeViewAt(tab.arguments.size)
|
||||
}
|
||||
|
||||
holder.itemView.actionChip.setOnClickListener {
|
||||
listener.onActionChipClicked(tab, holder.adapterPosition)
|
||||
binding.actionChip.setOnClickListener {
|
||||
listener.onActionChipClicked(tab, holder.bindingAdapterPosition)
|
||||
}
|
||||
|
||||
} else {
|
||||
holder.itemView.chipGroup.hide()
|
||||
binding.chipGroup.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,6 +158,4 @@ class TabAdapter(private var data: List<TabData>,
|
|||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
}
|
||||
|
|
|
@ -19,19 +19,17 @@ import android.view.ContextThemeWrapper
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.size
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.ItemAnnouncementBinding
|
||||
import com.keylesspalace.tusky.entity.Announcement
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import kotlinx.android.synthetic.main.item_announcement.view.*
|
||||
|
||||
|
||||
interface AnnouncementActionListener: LinkListener {
|
||||
fun openReactionPicker(announcementId: String, target: View)
|
||||
|
@ -44,16 +42,74 @@ class AnnouncementAdapter(
|
|||
private val listener: AnnouncementActionListener,
|
||||
private val wellbeingEnabled: Boolean = false,
|
||||
private val animateEmojis: Boolean = false
|
||||
) : RecyclerView.Adapter<AnnouncementAdapter.AnnouncementViewHolder>() {
|
||||
) : RecyclerView.Adapter<BindingHolder<ItemAnnouncementBinding>>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnnouncementViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_announcement, parent, false)
|
||||
return AnnouncementViewHolder(view)
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemAnnouncementBinding> {
|
||||
val binding = ItemAnnouncementBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return BindingHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(viewHolder: AnnouncementViewHolder, position: Int) {
|
||||
viewHolder.bind(items[position])
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemAnnouncementBinding>, position: Int) {
|
||||
val item = items[position]
|
||||
|
||||
val text = holder.binding.text
|
||||
val chips = holder.binding.chipGroup
|
||||
val addReactionChip = holder.binding.addReactionChip
|
||||
|
||||
LinkHelper.setClickableText(text, item.content, null, listener)
|
||||
|
||||
// If wellbeing mode is enabled, announcement badge counts should not be shown.
|
||||
if (wellbeingEnabled) {
|
||||
// Since reactions are not visible in wellbeing mode,
|
||||
// we shouldn't be able to add any ourselves.
|
||||
addReactionChip.visibility = View.GONE
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
.apply {
|
||||
val emojiText = if (reaction.url == null) {
|
||||
reaction.name
|
||||
} else {
|
||||
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
|
||||
)
|
||||
|
||||
isChecked = reaction.me
|
||||
|
||||
setOnClickListener {
|
||||
if (reaction.me) {
|
||||
listener.removeReaction(item.id, reaction.name)
|
||||
} else {
|
||||
listener.addReaction(item.id, reaction.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (chips.size - 1 > item.reactions.size) {
|
||||
chips.removeViewAt(item.reactions.size)
|
||||
}
|
||||
|
||||
addReactionChip.setOnClickListener {
|
||||
listener.openReactionPicker(item.id, it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
@ -62,67 +118,4 @@ class AnnouncementAdapter(
|
|||
this.items = items
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
inner class AnnouncementViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
|
||||
private val text: TextView = view.text
|
||||
private val chips: ChipGroup = view.chipGroup
|
||||
private val addReactionChip: Chip = view.addReactionChip
|
||||
|
||||
fun bind(item: Announcement) {
|
||||
LinkHelper.setClickableText(text, item.content, null, listener)
|
||||
|
||||
// If wellbeing mode is enabled, announcement badge counts should not be shown.
|
||||
if (wellbeingEnabled) {
|
||||
// Since reactions are not visible in wellbeing mode,
|
||||
// we shouldn't be able to add any ourselves.
|
||||
addReactionChip.visibility = View.GONE
|
||||
return
|
||||
}
|
||||
|
||||
item.reactions.forEachIndexed { i, reaction ->
|
||||
(chips.getChildAt(i)?.takeUnless { it.id == R.id.addReactionChip } as Chip?
|
||||
?: Chip(ContextThemeWrapper(view.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
|
||||
} else {
|
||||
view.context.getString(R.string.emoji_shortcode_format, reaction.name)
|
||||
}
|
||||
text = ("$emojiText ${reaction.count}")
|
||||
.emojify(
|
||||
listOf(Emoji(
|
||||
reaction.name,
|
||||
reaction.url ?: "",
|
||||
reaction.staticUrl ?: "",
|
||||
null
|
||||
)),
|
||||
this,
|
||||
animateEmojis
|
||||
)
|
||||
|
||||
isChecked = reaction.me
|
||||
|
||||
setOnClickListener {
|
||||
if (reaction.me) {
|
||||
listener.removeReaction(item.id, reaction.name)
|
||||
} else {
|
||||
listener.addReaction(item.id, reaction.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (chips.size - 1 > item.reactions.size) {
|
||||
chips.removeViewAt(item.reactions.size)
|
||||
}
|
||||
|
||||
addReactionChip.setOnClickListener {
|
||||
listener.openReactionPicker(item.id, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,13 +30,12 @@ import com.keylesspalace.tusky.R
|
|||
import com.keylesspalace.tusky.ViewTagActivity
|
||||
import com.keylesspalace.tusky.adapter.EmojiAdapter
|
||||
import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
|
||||
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.view.EmojiPicker
|
||||
import kotlinx.android.synthetic.main.activity_announcements.*
|
||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener, OnEmojiSelectedListener, Injectable {
|
||||
|
@ -46,6 +45,8 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener,
|
|||
|
||||
private val viewModel: AnnouncementsViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private val binding by viewBinding(ActivityAnnouncementsBinding::inflate)
|
||||
|
||||
private lateinit var adapter: AnnouncementAdapter
|
||||
|
||||
private val picker by lazy { EmojiPicker(this) }
|
||||
|
@ -63,22 +64,22 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener,
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_announcements)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
supportActionBar?.apply {
|
||||
title = getString(R.string.title_announcements)
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowHomeEnabled(true)
|
||||
}
|
||||
|
||||
swipeRefreshLayout.setOnRefreshListener(this::refreshAnnouncements)
|
||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
binding.swipeRefreshLayout.setOnRefreshListener(this::refreshAnnouncements)
|
||||
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
|
||||
announcementsList.setHasFixedSize(true)
|
||||
announcementsList.layoutManager = LinearLayoutManager(this)
|
||||
binding.announcementsList.setHasFixedSize(true)
|
||||
binding.announcementsList.layoutManager = LinearLayoutManager(this)
|
||||
val divider = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
|
||||
announcementsList.addItemDecoration(divider)
|
||||
binding.announcementsList.addItemDecoration(divider)
|
||||
|
||||
val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false)
|
||||
|
@ -86,31 +87,31 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener,
|
|||
|
||||
adapter = AnnouncementAdapter(emptyList(), this, wellbeingEnabled, animateEmojis)
|
||||
|
||||
announcementsList.adapter = adapter
|
||||
binding.announcementsList.adapter = adapter
|
||||
|
||||
viewModel.announcements.observe(this) {
|
||||
when (it) {
|
||||
is Success -> {
|
||||
progressBar.hide()
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
binding.progressBar.hide()
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
if (it.data.isNullOrEmpty()) {
|
||||
errorMessageView.setup(R.drawable.elephant_friend_empty, R.string.no_announcements)
|
||||
errorMessageView.show()
|
||||
binding.errorMessageView.setup(R.drawable.elephant_friend_empty, R.string.no_announcements)
|
||||
binding.errorMessageView.show()
|
||||
} else {
|
||||
errorMessageView.hide()
|
||||
binding.errorMessageView.hide()
|
||||
}
|
||||
adapter.updateList(it.data ?: listOf())
|
||||
}
|
||||
is Loading -> {
|
||||
errorMessageView.hide()
|
||||
binding.errorMessageView.hide()
|
||||
}
|
||||
is Error -> {
|
||||
progressBar.hide()
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
errorMessageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||
binding.progressBar.hide()
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
binding.errorMessageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||
refreshAnnouncements()
|
||||
}
|
||||
errorMessageView.show()
|
||||
binding.errorMessageView.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,12 +121,12 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener,
|
|||
}
|
||||
|
||||
viewModel.load()
|
||||
progressBar.show()
|
||||
binding.progressBar.show()
|
||||
}
|
||||
|
||||
private fun refreshAnnouncements() {
|
||||
viewModel.load()
|
||||
swipeRefreshLayout.isRefreshing = true
|
||||
binding.swipeRefreshLayout.isRefreshing = true
|
||||
}
|
||||
|
||||
override fun openReactionPicker(announcementId: String, target: View) {
|
||||
|
|
|
@ -18,7 +18,6 @@ package com.keylesspalace.tusky.components.compose
|
|||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.app.ProgressDialog
|
||||
import android.app.TimePickerDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
|
@ -61,6 +60,8 @@ import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
|
|||
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog
|
||||
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
|
||||
import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener
|
||||
import com.keylesspalace.tusky.components.compose.view.ComposeScheduleView
|
||||
import com.keylesspalace.tusky.databinding.ActivityComposeBinding
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
import com.keylesspalace.tusky.db.DraftAttachment
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
|
@ -75,11 +76,10 @@ 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 kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.activity_compose.*
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
@ -90,7 +90,7 @@ class ComposeActivity : BaseActivity(),
|
|||
OnEmojiSelectedListener,
|
||||
Injectable,
|
||||
InputConnectionCompat.OnCommitContentListener,
|
||||
TimePickerDialog.OnTimeSetListener {
|
||||
ComposeScheduleView.OnTimeSetListener {
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
@ -109,17 +109,20 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
private val viewModel: ComposeViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private val binding by viewBinding(ActivityComposeBinding::inflate)
|
||||
|
||||
private val maxUploadMediaNumber = 4
|
||||
private var mediaCount = 0
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT)
|
||||
if (theme == "black") {
|
||||
setTheme(R.style.TuskyDialogActivityBlackTheme)
|
||||
}
|
||||
setContentView(R.layout.activity_compose)
|
||||
setContentView(binding.root)
|
||||
|
||||
setupActionBar()
|
||||
// do not do anything when not logged in, activity will be finished in super.onCreate() anyway
|
||||
|
@ -135,10 +138,10 @@ class ComposeActivity : BaseActivity(),
|
|||
},
|
||||
onRemove = this::removeMediaFromQueue
|
||||
)
|
||||
composeMediaPreviewBar.layoutManager =
|
||||
binding.composeMediaPreviewBar.layoutManager =
|
||||
LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
|
||||
composeMediaPreviewBar.adapter = mediaAdapter
|
||||
composeMediaPreviewBar.itemAnimator = null
|
||||
binding.composeMediaPreviewBar.adapter = mediaAdapter
|
||||
binding.composeMediaPreviewBar.itemAnimator = null
|
||||
|
||||
subscribeToUpdates(mediaAdapter)
|
||||
setupButtons()
|
||||
|
@ -154,11 +157,11 @@ class ComposeActivity : BaseActivity(),
|
|||
setupReplyViews(composeOptions?.replyingStatusAuthor, composeOptions?.replyingStatusContent)
|
||||
val tootText = composeOptions?.tootText
|
||||
if (!tootText.isNullOrEmpty()) {
|
||||
composeEditField.setText(tootText)
|
||||
binding.composeEditField.setText(tootText)
|
||||
}
|
||||
|
||||
if (!composeOptions?.scheduledAt.isNullOrEmpty()) {
|
||||
composeScheduleView.setDateTime(composeOptions?.scheduledAt)
|
||||
binding.composeScheduleView.setDateTime(composeOptions?.scheduledAt)
|
||||
}
|
||||
|
||||
setupComposeField(preferences, viewModel.startingText)
|
||||
|
@ -198,14 +201,14 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
|
||||
if (shareBody.isNotBlank()) {
|
||||
val start = composeEditField.selectionStart.coerceAtLeast(0)
|
||||
val end = composeEditField.selectionEnd.coerceAtLeast(0)
|
||||
val start = binding.composeEditField.selectionStart.coerceAtLeast(0)
|
||||
val end = binding.composeEditField.selectionEnd.coerceAtLeast(0)
|
||||
val left = min(start, end)
|
||||
val right = max(start, end)
|
||||
composeEditField.text.replace(left, right, shareBody, 0, shareBody.length)
|
||||
binding.composeEditField.text.replace(left, right, shareBody, 0, shareBody.length)
|
||||
// move edittext cursor to first when shareBody parsed
|
||||
composeEditField.text.insert(0, "\n")
|
||||
composeEditField.setSelection(0)
|
||||
binding.composeEditField.text.insert(0, "\n")
|
||||
binding.composeEditField.setSelection(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -214,58 +217,58 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
private fun setupReplyViews(replyingStatusAuthor: String?, replyingStatusContent: String?) {
|
||||
if (replyingStatusAuthor != null) {
|
||||
composeReplyView.show()
|
||||
composeReplyView.text = getString(R.string.replying_to, replyingStatusAuthor)
|
||||
binding.composeReplyView.show()
|
||||
binding.composeReplyView.text = getString(R.string.replying_to, replyingStatusAuthor)
|
||||
val arrowDownIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_arrow_drop_down).apply { sizeDp = 12 }
|
||||
|
||||
ThemeUtils.setDrawableTint(this, arrowDownIcon, android.R.attr.textColorTertiary)
|
||||
composeReplyView.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, arrowDownIcon, null)
|
||||
binding.composeReplyView.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, arrowDownIcon, null)
|
||||
|
||||
composeReplyView.setOnClickListener {
|
||||
TransitionManager.beginDelayedTransition(composeReplyContentView.parent as ViewGroup)
|
||||
binding.composeReplyView.setOnClickListener {
|
||||
TransitionManager.beginDelayedTransition(binding.composeReplyContentView.parent as ViewGroup)
|
||||
|
||||
if (composeReplyContentView.isVisible) {
|
||||
composeReplyContentView.hide()
|
||||
composeReplyView.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, arrowDownIcon, null)
|
||||
if (binding.composeReplyContentView.isVisible) {
|
||||
binding.composeReplyContentView.hide()
|
||||
binding.composeReplyView.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, arrowDownIcon, null)
|
||||
} else {
|
||||
composeReplyContentView.show()
|
||||
binding.composeReplyContentView.show()
|
||||
val arrowUpIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_arrow_drop_up).apply { sizeDp = 12 }
|
||||
|
||||
ThemeUtils.setDrawableTint(this, arrowUpIcon, android.R.attr.textColorTertiary)
|
||||
composeReplyView.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, arrowUpIcon, null)
|
||||
binding.composeReplyView.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, arrowUpIcon, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
replyingStatusContent?.let { composeReplyContentView.text = it }
|
||||
replyingStatusContent?.let { binding.composeReplyContentView.text = it }
|
||||
}
|
||||
|
||||
private fun setupContentWarningField(startingContentWarning: String?) {
|
||||
if (startingContentWarning != null) {
|
||||
composeContentWarningField.setText(startingContentWarning)
|
||||
binding.composeContentWarningField.setText(startingContentWarning)
|
||||
}
|
||||
composeContentWarningField.onTextChanged { _, _, _, _ -> updateVisibleCharactersLeft() }
|
||||
binding.composeContentWarningField.onTextChanged { _, _, _, _ -> updateVisibleCharactersLeft() }
|
||||
}
|
||||
|
||||
private fun setupComposeField(preferences: SharedPreferences, startingText: String?) {
|
||||
composeEditField.setOnCommitContentListener(this)
|
||||
binding.composeEditField.setOnCommitContentListener(this)
|
||||
|
||||
composeEditField.setOnKeyListener { _, keyCode, event -> this.onKeyDown(keyCode, event) }
|
||||
binding.composeEditField.setOnKeyListener { _, keyCode, event -> this.onKeyDown(keyCode, event) }
|
||||
|
||||
composeEditField.setAdapter(
|
||||
binding.composeEditField.setAdapter(
|
||||
ComposeAutoCompleteAdapter(
|
||||
this,
|
||||
preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),
|
||||
preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||
)
|
||||
)
|
||||
composeEditField.setTokenizer(ComposeTokenizer())
|
||||
binding.composeEditField.setTokenizer(ComposeTokenizer())
|
||||
|
||||
composeEditField.setText(startingText)
|
||||
composeEditField.setSelection(composeEditField.length())
|
||||
binding.composeEditField.setText(startingText)
|
||||
binding.composeEditField.setSelection(binding.composeEditField.length())
|
||||
|
||||
val mentionColour = composeEditField.linkTextColors.defaultColor
|
||||
highlightSpans(composeEditField.text, mentionColour)
|
||||
composeEditField.afterTextChanged { editable ->
|
||||
val mentionColour = binding.composeEditField.linkTextColors.defaultColor
|
||||
highlightSpans(binding.composeEditField.text, mentionColour)
|
||||
binding.composeEditField.afterTextChanged { editable ->
|
||||
highlightSpans(editable, mentionColour)
|
||||
updateVisibleCharactersLeft()
|
||||
}
|
||||
|
@ -273,7 +276,7 @@ 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) {
|
||||
composeEditField.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
|
||||
binding.composeEditField.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -282,7 +285,7 @@ class ComposeActivity : BaseActivity(),
|
|||
viewModel.instanceParams.observe { instanceData ->
|
||||
maximumTootCharacters = instanceData.maxChars
|
||||
updateVisibleCharactersLeft()
|
||||
composeScheduleButton.visible(instanceData.supportsScheduled)
|
||||
binding.composeScheduleButton.visible(instanceData.supportsScheduled)
|
||||
}
|
||||
viewModel.emoji.observe { emoji -> setEmojiList(emoji) }
|
||||
combineLiveData(viewModel.markMediaAsSensitive, viewModel.showContentWarning) { markSensitive, showContentWarning ->
|
||||
|
@ -296,19 +299,19 @@ class ComposeActivity : BaseActivity(),
|
|||
mediaAdapter.submitList(media)
|
||||
if (media.size != mediaCount) {
|
||||
mediaCount = media.size
|
||||
composeMediaPreviewBar.visible(media.isNotEmpty())
|
||||
binding.composeMediaPreviewBar.visible(media.isNotEmpty())
|
||||
updateSensitiveMediaToggle(viewModel.markMediaAsSensitive.value != false, viewModel.showContentWarning.value != false)
|
||||
}
|
||||
}
|
||||
viewModel.poll.observe { poll ->
|
||||
pollPreview.visible(poll != null)
|
||||
poll?.let(pollPreview::setPoll)
|
||||
binding.pollPreview.visible(poll != null)
|
||||
poll?.let(binding.pollPreview::setPoll)
|
||||
}
|
||||
viewModel.scheduledAt.observe { scheduledAt ->
|
||||
if (scheduledAt == null) {
|
||||
composeScheduleView.resetSchedule()
|
||||
binding.composeScheduleView.resetSchedule()
|
||||
} else {
|
||||
composeScheduleView.setDateTime(scheduledAt)
|
||||
binding.composeScheduleView.setDateTime(scheduledAt)
|
||||
}
|
||||
updateScheduleButton()
|
||||
}
|
||||
|
@ -316,7 +319,7 @@ class ComposeActivity : BaseActivity(),
|
|||
val active = poll == null
|
||||
&& media!!.size != 4
|
||||
&& (media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE)
|
||||
enableButton(composeAddMediaButton, active, active)
|
||||
enableButton(binding.composeAddMediaButton, active, active)
|
||||
enablePollButton(media.isNullOrEmpty())
|
||||
}.subscribe()
|
||||
viewModel.uploadError.observe {
|
||||
|
@ -324,51 +327,52 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
viewModel.setupComplete.observe {
|
||||
// Focus may have changed during view model setup, ensure initial focus is on the edit field
|
||||
composeEditField.requestFocus()
|
||||
binding.composeEditField.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupButtons() {
|
||||
composeOptionsBottomSheet.listener = this
|
||||
binding.composeOptionsBottomSheet.listener = this
|
||||
|
||||
composeOptionsBehavior = BottomSheetBehavior.from(composeOptionsBottomSheet)
|
||||
addMediaBehavior = BottomSheetBehavior.from(addMediaBottomSheet)
|
||||
scheduleBehavior = BottomSheetBehavior.from(composeScheduleView)
|
||||
emojiBehavior = BottomSheetBehavior.from(emojiView)
|
||||
composeOptionsBehavior = BottomSheetBehavior.from(binding.composeOptionsBottomSheet)
|
||||
addMediaBehavior = BottomSheetBehavior.from(binding.addMediaBottomSheet)
|
||||
scheduleBehavior = BottomSheetBehavior.from(binding.composeScheduleView)
|
||||
emojiBehavior = BottomSheetBehavior.from(binding.emojiView)
|
||||
|
||||
enableButton(composeEmojiButton, clickable = false, colorActive = false)
|
||||
enableButton(binding.composeEmojiButton, clickable = false, colorActive = false)
|
||||
|
||||
// Setup the interface buttons.
|
||||
composeTootButton.setOnClickListener { onSendClicked() }
|
||||
composeAddMediaButton.setOnClickListener { openPickDialog() }
|
||||
composeToggleVisibilityButton.setOnClickListener { showComposeOptions() }
|
||||
composeContentWarningButton.setOnClickListener { onContentWarningChanged() }
|
||||
composeEmojiButton.setOnClickListener { showEmojis() }
|
||||
composeHideMediaButton.setOnClickListener { toggleHideMedia() }
|
||||
composeScheduleButton.setOnClickListener { onScheduleClick() }
|
||||
composeScheduleView.setResetOnClickListener { resetSchedule() }
|
||||
atButton.setOnClickListener { atButtonClicked() }
|
||||
hashButton.setOnClickListener { hashButtonClicked() }
|
||||
binding.composeTootButton.setOnClickListener { onSendClicked() }
|
||||
binding.composeAddMediaButton.setOnClickListener { openPickDialog() }
|
||||
binding.composeToggleVisibilityButton.setOnClickListener { showComposeOptions() }
|
||||
binding.composeContentWarningButton.setOnClickListener { onContentWarningChanged() }
|
||||
binding.composeEmojiButton.setOnClickListener { showEmojis() }
|
||||
binding.composeHideMediaButton.setOnClickListener { toggleHideMedia() }
|
||||
binding.composeScheduleButton.setOnClickListener { onScheduleClick() }
|
||||
binding.composeScheduleView.setResetOnClickListener { resetSchedule() }
|
||||
binding.composeScheduleView.setListener(this)
|
||||
binding.atButton.setOnClickListener { atButtonClicked() }
|
||||
binding.hashButton.setOnClickListener { hashButtonClicked() }
|
||||
|
||||
val textColor = ThemeUtils.getColor(this, android.R.attr.textColorTertiary)
|
||||
|
||||
val cameraIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_camera_alt).apply { colorInt = textColor; sizeDp = 18 }
|
||||
actionPhotoTake.setCompoundDrawablesRelativeWithIntrinsicBounds(cameraIcon, null, null, null)
|
||||
binding.actionPhotoTake.setCompoundDrawablesRelativeWithIntrinsicBounds(cameraIcon, null, null, null)
|
||||
|
||||
val imageIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_image).apply { colorInt = textColor; sizeDp = 18 }
|
||||
actionPhotoPick.setCompoundDrawablesRelativeWithIntrinsicBounds(imageIcon, null, null, null)
|
||||
binding.actionPhotoPick.setCompoundDrawablesRelativeWithIntrinsicBounds(imageIcon, null, null, null)
|
||||
|
||||
val pollIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_poll).apply { colorInt = textColor; sizeDp = 18 }
|
||||
addPollTextActionTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(pollIcon, null, null, null)
|
||||
binding.addPollTextActionTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(pollIcon, null, null, null)
|
||||
|
||||
actionPhotoTake.setOnClickListener { initiateCameraApp() }
|
||||
actionPhotoPick.setOnClickListener { onMediaPick() }
|
||||
addPollTextActionTextView.setOnClickListener { openPollDialog() }
|
||||
binding.actionPhotoTake.setOnClickListener { initiateCameraApp() }
|
||||
binding.actionPhotoPick.setOnClickListener { onMediaPick() }
|
||||
binding.addPollTextActionTextView.setOnClickListener { openPollDialog() }
|
||||
}
|
||||
|
||||
private fun setupActionBar() {
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
supportActionBar?.run {
|
||||
title = null
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
|
@ -387,40 +391,40 @@ class ComposeActivity : BaseActivity(),
|
|||
val animateAvatars = preferences.getBoolean("animateGifAvatars", false)
|
||||
loadAvatar(
|
||||
activeAccount.profilePictureUrl,
|
||||
composeAvatar,
|
||||
binding.composeAvatar,
|
||||
avatarSize / 8,
|
||||
animateAvatars
|
||||
)
|
||||
composeAvatar.contentDescription = getString(R.string.compose_active_account_description,
|
||||
binding.composeAvatar.contentDescription = getString(R.string.compose_active_account_description,
|
||||
activeAccount.fullName)
|
||||
}
|
||||
|
||||
private fun replaceTextAtCaret(text: CharSequence) {
|
||||
// If you select "backward" in an editable, you get SelectionStart > SelectionEnd
|
||||
val start = composeEditField.selectionStart.coerceAtMost(composeEditField.selectionEnd)
|
||||
val end = composeEditField.selectionStart.coerceAtLeast(composeEditField.selectionEnd)
|
||||
val textToInsert = if (start > 0 && !composeEditField.text[start - 1].isWhitespace()) {
|
||||
val start = binding.composeEditField.selectionStart.coerceAtMost(binding.composeEditField.selectionEnd)
|
||||
val end = binding.composeEditField.selectionStart.coerceAtLeast(binding.composeEditField.selectionEnd)
|
||||
val textToInsert = if (start > 0 && !binding.composeEditField.text[start - 1].isWhitespace()) {
|
||||
" $text"
|
||||
} else {
|
||||
text
|
||||
}
|
||||
composeEditField.text.replace(start, end, textToInsert)
|
||||
binding.composeEditField.text.replace(start, end, textToInsert)
|
||||
|
||||
// Set the cursor after the inserted text
|
||||
composeEditField.setSelection(start + text.length)
|
||||
binding.composeEditField.setSelection(start + text.length)
|
||||
}
|
||||
|
||||
fun prependSelectedWordsWith(text: CharSequence) {
|
||||
// If you select "backward" in an editable, you get SelectionStart > SelectionEnd
|
||||
val start = composeEditField.selectionStart.coerceAtMost(composeEditField.selectionEnd)
|
||||
val end = composeEditField.selectionStart.coerceAtLeast(composeEditField.selectionEnd)
|
||||
val editorText = composeEditField.text
|
||||
val start = binding.composeEditField.selectionStart.coerceAtMost(binding.composeEditField.selectionEnd)
|
||||
val end = binding.composeEditField.selectionStart.coerceAtLeast(binding.composeEditField.selectionEnd)
|
||||
val editorText = binding.composeEditField.text
|
||||
|
||||
if (start == end) {
|
||||
// No selection, just insert text at caret
|
||||
editorText.insert(start, text)
|
||||
// Set the cursor after the inserted text
|
||||
composeEditField.setSelection(start + text.length)
|
||||
binding.composeEditField.setSelection(start + text.length)
|
||||
} else {
|
||||
var wasWord: Boolean
|
||||
var isWord = end < editorText.length && !Character.isWhitespace(editorText[end])
|
||||
|
@ -446,7 +450,7 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
|
||||
// Keep the same text (including insertions) selected
|
||||
composeEditField.setSelection(start, newEnd)
|
||||
binding.composeEditField.setSelection(start, newEnd)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -465,7 +469,7 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
|
||||
private fun displayTransientError(@StringRes stringId: Int) {
|
||||
val bar = Snackbar.make(activityCompose, stringId, Snackbar.LENGTH_LONG)
|
||||
val bar = Snackbar.make(binding.activityCompose, stringId, Snackbar.LENGTH_LONG)
|
||||
//necessary so snackbar is shown over everything
|
||||
bar.view.elevation = resources.getDimension(R.dimen.compose_activity_snackbar_elevation)
|
||||
bar.show()
|
||||
|
@ -477,49 +481,49 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
private fun updateSensitiveMediaToggle(markMediaSensitive: Boolean, contentWarningShown: Boolean) {
|
||||
if (viewModel.media.value.isNullOrEmpty()) {
|
||||
composeHideMediaButton.hide()
|
||||
binding.composeHideMediaButton.hide()
|
||||
} else {
|
||||
composeHideMediaButton.show()
|
||||
binding.composeHideMediaButton.show()
|
||||
@ColorInt val color = if (contentWarningShown) {
|
||||
composeHideMediaButton.setImageResource(R.drawable.ic_hide_media_24dp)
|
||||
composeHideMediaButton.isClickable = false
|
||||
binding.composeHideMediaButton.setImageResource(R.drawable.ic_hide_media_24dp)
|
||||
binding.composeHideMediaButton.isClickable = false
|
||||
ContextCompat.getColor(this, R.color.transparent_tusky_blue)
|
||||
|
||||
} else {
|
||||
composeHideMediaButton.isClickable = true
|
||||
binding.composeHideMediaButton.isClickable = true
|
||||
if (markMediaSensitive) {
|
||||
composeHideMediaButton.setImageResource(R.drawable.ic_hide_media_24dp)
|
||||
binding.composeHideMediaButton.setImageResource(R.drawable.ic_hide_media_24dp)
|
||||
ContextCompat.getColor(this, R.color.tusky_blue)
|
||||
} else {
|
||||
composeHideMediaButton.setImageResource(R.drawable.ic_eye_24dp)
|
||||
binding.composeHideMediaButton.setImageResource(R.drawable.ic_eye_24dp)
|
||||
ThemeUtils.getColor(this, android.R.attr.textColorTertiary)
|
||||
}
|
||||
}
|
||||
composeHideMediaButton.drawable.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
|
||||
binding.composeHideMediaButton.drawable.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateScheduleButton() {
|
||||
@ColorInt val color = if (composeScheduleView.time == null) {
|
||||
@ColorInt val color = if (binding.composeScheduleView.time == null) {
|
||||
ThemeUtils.getColor(this, android.R.attr.textColorTertiary)
|
||||
} else {
|
||||
ContextCompat.getColor(this, R.color.tusky_blue)
|
||||
}
|
||||
composeScheduleButton.drawable.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
|
||||
binding.composeScheduleButton.drawable.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
|
||||
private fun enableButtons(enable: Boolean) {
|
||||
composeAddMediaButton.isClickable = enable
|
||||
composeToggleVisibilityButton.isClickable = enable
|
||||
composeEmojiButton.isClickable = enable
|
||||
composeHideMediaButton.isClickable = enable
|
||||
composeScheduleButton.isClickable = enable
|
||||
composeTootButton.isEnabled = enable
|
||||
binding.composeAddMediaButton.isClickable = enable
|
||||
binding.composeToggleVisibilityButton.isClickable = enable
|
||||
binding.composeEmojiButton.isClickable = enable
|
||||
binding.composeHideMediaButton.isClickable = enable
|
||||
binding.composeScheduleButton.isClickable = enable
|
||||
binding.composeTootButton.isEnabled = enable
|
||||
}
|
||||
|
||||
private fun setStatusVisibility(visibility: Status.Visibility) {
|
||||
composeOptionsBottomSheet.setStatusVisibility(visibility)
|
||||
composeTootButton.setStatusVisibility(visibility)
|
||||
binding.composeOptionsBottomSheet.setStatusVisibility(visibility)
|
||||
binding.composeTootButton.setStatusVisibility(visibility)
|
||||
|
||||
val iconRes = when (visibility) {
|
||||
Status.Visibility.PUBLIC -> R.drawable.ic_public_24dp
|
||||
|
@ -528,7 +532,7 @@ class ComposeActivity : BaseActivity(),
|
|||
Status.Visibility.UNLISTED -> R.drawable.ic_lock_open_24dp
|
||||
else -> R.drawable.ic_lock_open_24dp
|
||||
}
|
||||
composeToggleVisibilityButton.setImageResource(iconRes)
|
||||
binding.composeToggleVisibilityButton.setImageResource(iconRes)
|
||||
}
|
||||
|
||||
private fun showComposeOptions() {
|
||||
|
@ -544,7 +548,7 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
private fun onScheduleClick() {
|
||||
if (viewModel.scheduledAt.value == null) {
|
||||
composeScheduleView.openPickDateDialog()
|
||||
binding.composeScheduleView.openPickDateDialog()
|
||||
} else {
|
||||
showScheduleView()
|
||||
}
|
||||
|
@ -562,7 +566,7 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
|
||||
private fun showEmojis() {
|
||||
emojiView.adapter?.let {
|
||||
binding.emojiView.adapter?.let {
|
||||
if (it.itemCount == 0) {
|
||||
val errorMessage = getString(R.string.error_no_custom_emojis, accountManager.activeAccount!!.domain)
|
||||
Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT).show()
|
||||
|
@ -625,10 +629,10 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
val layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
layoutParams.setMargins(margin, margin, margin, marginBottom)
|
||||
pollPreview.layoutParams = layoutParams
|
||||
binding.pollPreview.layoutParams = layoutParams
|
||||
|
||||
pollPreview.setOnClickListener {
|
||||
val popup = PopupMenu(this, pollPreview)
|
||||
binding.pollPreview.setOnClickListener {
|
||||
val popup = PopupMenu(this, binding.pollPreview)
|
||||
val editId = 1
|
||||
val removeId = 2
|
||||
popup.menu.add(0, editId, 0, R.string.edit_poll)
|
||||
|
@ -646,7 +650,7 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
private fun removePoll() {
|
||||
viewModel.poll.value = null
|
||||
pollPreview.hide()
|
||||
binding.pollPreview.hide()
|
||||
}
|
||||
|
||||
override fun onVisibilityChanged(visibility: Status.Visibility) {
|
||||
|
@ -657,39 +661,39 @@ class ComposeActivity : BaseActivity(),
|
|||
@VisibleForTesting
|
||||
fun calculateTextLength(): Int {
|
||||
var offset = 0
|
||||
val urlSpans = composeEditField.urls
|
||||
val urlSpans = binding.composeEditField.urls
|
||||
if (urlSpans != null) {
|
||||
for (span in urlSpans) {
|
||||
offset += max(0, span.url.length - MAXIMUM_URL_LENGTH)
|
||||
}
|
||||
}
|
||||
var length = composeEditField.length() - offset
|
||||
var length = binding.composeEditField.length() - offset
|
||||
if (viewModel.showContentWarning.value!!) {
|
||||
length += composeContentWarningField.length()
|
||||
length += binding.composeContentWarningField.length()
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
||||
private fun updateVisibleCharactersLeft() {
|
||||
val remainingLength = maximumTootCharacters - calculateTextLength()
|
||||
composeCharactersLeftView.text = String.format(Locale.getDefault(), "%d", remainingLength)
|
||||
binding.composeCharactersLeftView.text = String.format(Locale.getDefault(), "%d", remainingLength)
|
||||
|
||||
val textColor = if (remainingLength < 0) {
|
||||
ContextCompat.getColor(this, R.color.tusky_red)
|
||||
} else {
|
||||
ThemeUtils.getColor(this, android.R.attr.textColorTertiary)
|
||||
}
|
||||
composeCharactersLeftView.setTextColor(textColor)
|
||||
binding.composeCharactersLeftView.setTextColor(textColor)
|
||||
}
|
||||
|
||||
private fun onContentWarningChanged() {
|
||||
val showWarning = composeContentWarningBar.isGone
|
||||
val showWarning = binding.composeContentWarningBar.isGone
|
||||
viewModel.contentWarningChanged(showWarning)
|
||||
updateVisibleCharactersLeft()
|
||||
}
|
||||
|
||||
private fun verifyScheduledTime(): Boolean {
|
||||
return composeScheduleView.verifyScheduledTime(composeScheduleView.getDateTime(viewModel.scheduledAt.value))
|
||||
return binding.composeScheduleView.verifyScheduledTime(binding.composeScheduleView.getDateTime(viewModel.scheduledAt.value))
|
||||
}
|
||||
|
||||
private fun onSendClicked() {
|
||||
|
@ -724,14 +728,14 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
private fun sendStatus() {
|
||||
enableButtons(false)
|
||||
val contentText = composeEditField.text.toString()
|
||||
val contentText = binding.composeEditField.text.toString()
|
||||
var spoilerText = ""
|
||||
if (viewModel.showContentWarning.value!!) {
|
||||
spoilerText = composeContentWarningField.text.toString()
|
||||
spoilerText = binding.composeContentWarningField.text.toString()
|
||||
}
|
||||
val characterCount = calculateTextLength()
|
||||
if ((characterCount <= 0 || contentText.isBlank()) && viewModel.media.value!!.isEmpty()) {
|
||||
composeEditField.error = getString(R.string.error_empty)
|
||||
binding.composeEditField.error = getString(R.string.error_empty)
|
||||
enableButtons(true)
|
||||
} else if (characterCount <= maximumTootCharacters) {
|
||||
if (viewModel.media.value!!.isNotEmpty()) {
|
||||
|
@ -746,7 +750,7 @@ class ComposeActivity : BaseActivity(),
|
|||
})
|
||||
|
||||
} else {
|
||||
composeEditField.error = getString(R.string.error_compose_character_limit)
|
||||
binding.composeEditField.error = getString(R.string.error_compose_character_limit)
|
||||
enableButtons(true)
|
||||
}
|
||||
}
|
||||
|
@ -757,7 +761,7 @@ class ComposeActivity : BaseActivity(),
|
|||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
initiateMediaPicking()
|
||||
} else {
|
||||
val bar = Snackbar.make(activityCompose, R.string.error_media_upload_permission,
|
||||
val bar = Snackbar.make(binding.activityCompose, R.string.error_media_upload_permission,
|
||||
Snackbar.LENGTH_SHORT).apply {
|
||||
|
||||
}
|
||||
|
@ -812,12 +816,12 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
|
||||
private fun enablePollButton(enable: Boolean) {
|
||||
addPollTextActionTextView.isEnabled = enable
|
||||
binding.addPollTextActionTextView.isEnabled = enable
|
||||
val textColor = ThemeUtils.getColor(this,
|
||||
if (enable) android.R.attr.textColorTertiary
|
||||
else R.attr.textColorDisabled)
|
||||
addPollTextActionTextView.setTextColor(textColor)
|
||||
addPollTextActionTextView.compoundDrawablesRelative[0].colorFilter = PorterDuffColorFilter(textColor, PorterDuff.Mode.SRC_IN)
|
||||
binding.addPollTextActionTextView.setTextColor(textColor)
|
||||
binding.addPollTextActionTextView.compoundDrawablesRelative[0].colorFilter = PorterDuffColorFilter(textColor, PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
|
||||
private fun removeMediaFromQueue(item: QueuedMedia) {
|
||||
|
@ -835,7 +839,7 @@ class ComposeActivity : BaseActivity(),
|
|||
val count = clipData.itemCount
|
||||
if (mediaCount + count > maxUploadMediaNumber) {
|
||||
// check if exist media + upcoming media > 4, then prob error message.
|
||||
Toast.makeText(this, getString(R.string.error_upload_max_media_reached, maxUploadMediaNumber), Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(this, resources.getQuantityString(R.plurals.error_upload_max_media_reached, maxUploadMediaNumber, maxUploadMediaNumber), Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
// if not grater then 4, upload all multiple media.
|
||||
for (i in 0 until count) {
|
||||
|
@ -878,19 +882,18 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
|
||||
private fun showContentWarning(show: Boolean) {
|
||||
TransitionManager.beginDelayedTransition(composeContentWarningBar.parent as ViewGroup)
|
||||
TransitionManager.beginDelayedTransition(binding.composeContentWarningBar.parent as ViewGroup)
|
||||
@ColorInt val color = if (show) {
|
||||
composeContentWarningBar.show()
|
||||
composeContentWarningField.setSelection(composeContentWarningField.text.length)
|
||||
composeContentWarningField.requestFocus()
|
||||
binding.composeContentWarningBar.show()
|
||||
binding.composeContentWarningField.setSelection(binding.composeContentWarningField.text.length)
|
||||
binding.composeContentWarningField.requestFocus()
|
||||
ContextCompat.getColor(this, R.color.tusky_blue)
|
||||
} else {
|
||||
composeContentWarningBar.hide()
|
||||
composeEditField.requestFocus()
|
||||
binding.composeContentWarningBar.hide()
|
||||
binding.composeEditField.requestFocus()
|
||||
ThemeUtils.getColor(this, android.R.attr.textColorTertiary)
|
||||
}
|
||||
composeContentWarningButton.drawable.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
|
||||
|
||||
binding.composeContentWarningButton.drawable.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
|
@ -938,8 +941,8 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
|
||||
private fun handleCloseButton() {
|
||||
val contentText = composeEditField.text.toString()
|
||||
val contentWarning = composeContentWarningField.text.toString()
|
||||
val contentText = binding.composeEditField.text.toString()
|
||||
val contentWarning = binding.composeContentWarningField.text.toString()
|
||||
if (viewModel.didChange(contentText, contentWarning)) {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.compose_save_draft)
|
||||
|
@ -973,8 +976,8 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
private fun setEmojiList(emojiList: List<Emoji>?) {
|
||||
if (emojiList != null) {
|
||||
emojiView.adapter = EmojiAdapter(emojiList, this@ComposeActivity)
|
||||
enableButton(composeEmojiButton, true, emojiList.isNotEmpty())
|
||||
binding.emojiView.adapter = EmojiAdapter(emojiList, this@ComposeActivity)
|
||||
enableButton(binding.composeEmojiButton, true, emojiList.isNotEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -992,9 +995,8 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
}
|
||||
|
||||
override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) {
|
||||
composeScheduleView.onTimeSet(hourOfDay, minute)
|
||||
viewModel.updateScheduledAt(composeScheduleView.time)
|
||||
override fun onTimeSet(time: String) {
|
||||
viewModel.updateScheduledAt(time)
|
||||
if (verifyScheduledTime()) {
|
||||
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
} else {
|
||||
|
|
|
@ -103,7 +103,7 @@ class MediaPreviewAdapter(
|
|||
progressImageView.layoutParams = layoutParams
|
||||
progressImageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
progressImageView.setOnClickListener {
|
||||
onMediaClick(adapterPosition, progressImageView)
|
||||
onMediaClick(bindingAdapterPosition, progressImageView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,8 @@ import android.view.LayoutInflater
|
|||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.adapter.AddPollOptionsAdapter
|
||||
import com.keylesspalace.tusky.databinding.DialogAddPollBinding
|
||||
import com.keylesspalace.tusky.entity.NewPoll
|
||||
import kotlinx.android.synthetic.main.dialog_add_poll.view.*
|
||||
|
||||
fun showAddPollDialog(
|
||||
context: Context,
|
||||
|
@ -34,12 +33,12 @@ fun showAddPollDialog(
|
|||
onUpdatePoll: (NewPoll) -> Unit
|
||||
) {
|
||||
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.dialog_add_poll, null)
|
||||
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(view)
|
||||
.setView(binding.root)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create()
|
||||
|
@ -48,7 +47,7 @@ fun showAddPollDialog(
|
|||
options = poll?.options?.toMutableList() ?: mutableListOf("", ""),
|
||||
maxOptionLength = maxOptionLength,
|
||||
onOptionRemoved = { valid ->
|
||||
view.addChoiceButton.isEnabled = true
|
||||
binding.addChoiceButton.isEnabled = true
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = valid
|
||||
},
|
||||
onOptionChanged = { valid ->
|
||||
|
@ -56,9 +55,9 @@ fun showAddPollDialog(
|
|||
}
|
||||
)
|
||||
|
||||
view.pollChoices.adapter = adapter
|
||||
binding.pollChoices.adapter = adapter
|
||||
|
||||
view.addChoiceButton.setOnClickListener {
|
||||
binding.addChoiceButton.setOnClickListener {
|
||||
if (adapter.itemCount < maxOptionCount) {
|
||||
adapter.addChoice()
|
||||
}
|
||||
|
@ -71,14 +70,14 @@ fun showAddPollDialog(
|
|||
it <= poll?.expiresIn ?: 0
|
||||
}
|
||||
|
||||
view.pollDurationSpinner.setSelection(pollDurationId)
|
||||
binding.pollDurationSpinner.setSelection(pollDurationId)
|
||||
|
||||
view.multipleChoicesCheckBox.isChecked = poll?.multiple ?: false
|
||||
binding.multipleChoicesCheckBox.isChecked = poll?.multiple ?: false
|
||||
|
||||
dialog.setOnShowListener {
|
||||
val button = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||
button.setOnClickListener {
|
||||
val selectedPollDurationId = view.pollDurationSpinner.selectedItemPosition
|
||||
val selectedPollDurationId = binding.pollDurationSpinner.selectedItemPosition
|
||||
|
||||
val pollDuration = context.resources
|
||||
.getIntArray(R.array.poll_duration_values)[selectedPollDurationId]
|
||||
|
@ -86,7 +85,7 @@ fun showAddPollDialog(
|
|||
onUpdatePoll(NewPoll(
|
||||
options = adapter.pollOptions,
|
||||
expiresIn = pollDuration,
|
||||
multiple = view.multipleChoicesCheckBox.isChecked
|
||||
multiple = binding.multipleChoicesCheckBox.isChecked
|
||||
))
|
||||
|
||||
dialog.dismiss()
|
||||
|
@ -97,5 +96,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)
|
||||
|
||||
}
|
|
@ -13,17 +13,16 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.adapter
|
||||
package com.keylesspalace.tusky.components.compose.dialog
|
||||
|
||||
import android.text.InputFilter
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.ItemAddPollOptionBinding
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
import com.keylesspalace.tusky.util.onTextChanged
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
|
||||
|
@ -32,7 +31,7 @@ class AddPollOptionsAdapter(
|
|||
private val maxOptionLength: Int,
|
||||
private val onOptionRemoved: (Boolean) -> Unit,
|
||||
private val onOptionChanged: (Boolean) -> Unit
|
||||
): RecyclerView.Adapter<ViewHolder>() {
|
||||
): RecyclerView.Adapter<BindingHolder<ItemAddPollOptionBinding>>() {
|
||||
|
||||
val pollOptions: List<String>
|
||||
get() = options.toList()
|
||||
|
@ -42,11 +41,12 @@ class AddPollOptionsAdapter(
|
|||
notifyItemInserted(options.size - 1)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val holder = ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_add_poll_option, parent, false))
|
||||
holder.editText.filters = arrayOf(InputFilter.LengthFilter(maxOptionLength))
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemAddPollOptionBinding> {
|
||||
val binding = ItemAddPollOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
val holder = BindingHolder(binding)
|
||||
binding.optionEditText.filters = arrayOf(InputFilter.LengthFilter(maxOptionLength))
|
||||
|
||||
holder.editText.onTextChanged { s, _, _, _ ->
|
||||
binding.optionEditText.onTextChanged { s, _, _, _ ->
|
||||
val pos = holder.adapterPosition
|
||||
if(pos != RecyclerView.NO_POSITION) {
|
||||
options[pos] = s.toString()
|
||||
|
@ -59,15 +59,15 @@ class AddPollOptionsAdapter(
|
|||
|
||||
override fun getItemCount() = options.size
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.editText.setText(options[position])
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemAddPollOptionBinding>, position: Int) {
|
||||
holder.binding.optionEditText.setText(options[position])
|
||||
|
||||
holder.textInputLayout.hint = holder.textInputLayout.context.getString(R.string.poll_new_choice_hint, position + 1)
|
||||
holder.binding.optionTextInputLayout.hint = holder.binding.root.context.getString(R.string.poll_new_choice_hint, position + 1)
|
||||
|
||||
holder.deleteButton.visible(position > 1, View.INVISIBLE)
|
||||
holder.binding.deleteButton.visible(position > 1, View.INVISIBLE)
|
||||
|
||||
holder.deleteButton.setOnClickListener {
|
||||
holder.editText.clearFocus()
|
||||
holder.binding.deleteButton.setOnClickListener {
|
||||
holder.binding.optionEditText.clearFocus()
|
||||
options.removeAt(holder.adapterPosition)
|
||||
notifyItemRemoved(holder.adapterPosition)
|
||||
onOptionRemoved(validateInput())
|
||||
|
@ -81,12 +81,4 @@ class AddPollOptionsAdapter(
|
|||
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
||||
val textInputLayout: TextInputLayout = itemView.findViewById(R.id.optionTextInputLayout)
|
||||
val editText: TextInputEditText = itemView.findViewById(R.id.optionEditText)
|
||||
val deleteButton: ImageButton = itemView.findViewById(R.id.deleteButton)
|
||||
}
|
|
@ -63,8 +63,8 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
|
|||
(imageView.layoutParams as LinearLayout.LayoutParams).setMargins(0, margin, 0, 0)
|
||||
|
||||
val input = EditText(this)
|
||||
input.hint = getString(R.string.hint_describe_for_visually_impaired,
|
||||
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)
|
||||
|
|
|
@ -17,7 +17,6 @@ package com.keylesspalace.tusky.components.compose.view;
|
|||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
@ -31,8 +30,9 @@ import androidx.core.content.ContextCompat;
|
|||
import com.google.android.material.datepicker.CalendarConstraints;
|
||||
import com.google.android.material.datepicker.DateValidatorPointForward;
|
||||
import com.google.android.material.datepicker.MaterialDatePicker;
|
||||
import com.google.android.material.timepicker.MaterialTimePicker;
|
||||
import com.google.android.material.timepicker.TimeFormat;
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.fragment.TimePickerFragment;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
|
@ -44,6 +44,12 @@ import java.util.TimeZone;
|
|||
|
||||
public class ComposeScheduleView extends ConstraintLayout {
|
||||
|
||||
public interface OnTimeSetListener {
|
||||
void onTimeSet(String time);
|
||||
}
|
||||
|
||||
private OnTimeSetListener listener;
|
||||
|
||||
private DateFormat dateFormat;
|
||||
private DateFormat timeFormat;
|
||||
private SimpleDateFormat iso8601;
|
||||
|
@ -92,6 +98,10 @@ public class ComposeScheduleView extends ConstraintLayout {
|
|||
setEditIcons();
|
||||
}
|
||||
|
||||
public void setListener(OnTimeSetListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private void setScheduledDateTime() {
|
||||
if (scheduleDateTime == null) {
|
||||
scheduledDateTimeView.setText("");
|
||||
|
@ -144,13 +154,20 @@ public class ComposeScheduleView extends ConstraintLayout {
|
|||
}
|
||||
|
||||
private void openPickTimeDialog() {
|
||||
TimePickerFragment picker = new TimePickerFragment();
|
||||
MaterialTimePicker.Builder pickerBuilder = new MaterialTimePicker.Builder();
|
||||
if (scheduleDateTime != null) {
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(TimePickerFragment.PICKER_TIME_HOUR, scheduleDateTime.get(Calendar.HOUR_OF_DAY));
|
||||
args.putInt(TimePickerFragment.PICKER_TIME_MINUTE, scheduleDateTime.get(Calendar.MINUTE));
|
||||
picker.setArguments(args);
|
||||
pickerBuilder.setHour(scheduleDateTime.get(Calendar.HOUR_OF_DAY))
|
||||
.setMinute(scheduleDateTime.get(Calendar.MINUTE));
|
||||
}
|
||||
if (android.text.format.DateFormat.is24HourFormat(this.getContext())) {
|
||||
pickerBuilder.setTimeFormat(TimeFormat.CLOCK_24H);
|
||||
} else {
|
||||
pickerBuilder.setTimeFormat(TimeFormat.CLOCK_12H);
|
||||
}
|
||||
|
||||
MaterialTimePicker picker = pickerBuilder.build();
|
||||
picker.addOnPositiveButtonClickListener(v -> onTimeSet(picker.getHour(), picker.getMinute()));
|
||||
|
||||
picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(), "time_picker");
|
||||
}
|
||||
|
||||
|
@ -200,11 +217,14 @@ public class ComposeScheduleView extends ConstraintLayout {
|
|||
openPickTimeDialog();
|
||||
}
|
||||
|
||||
public void onTimeSet(int hourOfDay, int minute) {
|
||||
private void onTimeSet(int hourOfDay, int minute) {
|
||||
initializeSuggestedTime();
|
||||
scheduleDateTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
|
||||
scheduleDateTime.set(Calendar.MINUTE, minute);
|
||||
setScheduledDateTime();
|
||||
if (listener != null) {
|
||||
listener.onTimeSet(getTime());
|
||||
}
|
||||
}
|
||||
|
||||
public String getTime() {
|
||||
|
|
|
@ -17,11 +17,12 @@ package com.keylesspalace.tusky.components.compose.view
|
|||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.adapter.PreviewPollOptionsAdapter
|
||||
import com.keylesspalace.tusky.databinding.ViewPollPreviewBinding
|
||||
import com.keylesspalace.tusky.entity.NewPoll
|
||||
import kotlinx.android.synthetic.main.view_poll_preview.view.*
|
||||
|
||||
class PollPreviewView @JvmOverloads constructor(
|
||||
context: Context?,
|
||||
|
@ -29,11 +30,11 @@ class PollPreviewView @JvmOverloads constructor(
|
|||
defStyleAttr: Int = 0)
|
||||
: LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
val adapter = PreviewPollOptionsAdapter()
|
||||
private val adapter = PreviewPollOptionsAdapter()
|
||||
|
||||
private val binding = ViewPollPreviewBinding.inflate(LayoutInflater.from(context), this)
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.view_poll_preview, this)
|
||||
|
||||
orientation = VERTICAL
|
||||
|
||||
setBackgroundResource(R.drawable.card_frame)
|
||||
|
@ -42,8 +43,7 @@ class PollPreviewView @JvmOverloads constructor(
|
|||
|
||||
setPadding(padding, padding, padding, padding)
|
||||
|
||||
pollPreviewOptions.adapter = adapter
|
||||
|
||||
binding.pollPreviewOptions.adapter = adapter
|
||||
}
|
||||
|
||||
fun setPoll(poll: NewPoll){
|
||||
|
@ -52,13 +52,11 @@ class PollPreviewView @JvmOverloads constructor(
|
|||
val pollDurationId = resources.getIntArray(R.array.poll_duration_values).indexOfLast {
|
||||
it <= poll.expiresIn
|
||||
}
|
||||
pollDurationPreview.text = resources.getStringArray(R.array.poll_duration_names)[pollDurationId]
|
||||
|
||||
binding.pollDurationPreview.text = resources.getStringArray(R.array.poll_duration_names)[pollDurationId]
|
||||
}
|
||||
|
||||
override fun setOnClickListener(l: OnClickListener?) {
|
||||
super.setOnClickListener(l)
|
||||
adapter.setOnClickListener(l)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,3 +1,18 @@
|
|||
/* Copyright 2021 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.components.conversation
|
||||
|
||||
import android.view.LayoutInflater
|
||||
|
@ -10,6 +25,7 @@ import androidx.recyclerview.widget.ListUpdateCallback
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.adapter.NetworkStateViewHolder
|
||||
import com.keylesspalace.tusky.databinding.ItemNetworkStateBinding
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
||||
import com.keylesspalace.tusky.util.NetworkState
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
|
@ -49,11 +65,15 @@ class ConversationAdapter(
|
|||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||
return when (viewType) {
|
||||
R.layout.item_network_state -> NetworkStateViewHolder(view, retryCallback)
|
||||
R.layout.item_conversation -> ConversationViewHolder(view, statusDisplayOptions,
|
||||
listener)
|
||||
R.layout.item_network_state -> {
|
||||
val binding = ItemNetworkStateBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
NetworkStateViewHolder(binding, retryCallback)
|
||||
}
|
||||
R.layout.item_conversation -> {
|
||||
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||
ConversationViewHolder(view, statusDisplayOptions, listener)
|
||||
}
|
||||
else -> throw IllegalArgumentException("unknown view type $viewType")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ fun Account.toEntity() =
|
|||
ConversationAccountEntity(
|
||||
id,
|
||||
username,
|
||||
displayName.orEmpty(),
|
||||
name,
|
||||
avatar,
|
||||
emojis ?: emptyList()
|
||||
)
|
||||
|
|
|
@ -147,7 +147,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
|
|||
/* input filter for TextViews have to be set before text */
|
||||
if (collapsible && (expanded || TextUtils.isEmpty(spoilerText))) {
|
||||
contentCollapseButton.setOnClickListener(view -> {
|
||||
int position = getAdapterPosition();
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION)
|
||||
listener.onContentCollapsedChange(!collapsed, position);
|
||||
});
|
||||
|
|
|
@ -28,6 +28,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator
|
|||
import com.keylesspalace.tusky.AccountActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.ViewTagActivity
|
||||
import com.keylesspalace.tusky.databinding.FragmentTimelineBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.fragment.SFragment
|
||||
|
@ -38,7 +39,7 @@ import com.keylesspalace.tusky.util.CardViewMode
|
|||
import com.keylesspalace.tusky.util.NetworkState
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import kotlinx.android.synthetic.main.fragment_timeline.*
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import javax.inject.Inject
|
||||
|
||||
class ConversationsFragment : SFragment(), StatusActionListener, Injectable, ReselectableFragment {
|
||||
|
@ -48,6 +49,8 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
|||
|
||||
private val viewModel: ConversationsViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private val binding by viewBinding(FragmentTimelineBinding::bind)
|
||||
|
||||
private lateinit var adapter: ConversationAdapter
|
||||
|
||||
private var layoutManager: LinearLayoutManager? = null
|
||||
|
@ -73,14 +76,14 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
|||
|
||||
adapter = ConversationAdapter(statusDisplayOptions, this, ::onTopLoaded, viewModel::retry)
|
||||
|
||||
recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
||||
binding.recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
||||
layoutManager = LinearLayoutManager(view.context)
|
||||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.adapter = adapter
|
||||
(recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
binding.recyclerView.layoutManager = layoutManager
|
||||
binding.recyclerView.adapter = adapter
|
||||
(binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
|
||||
progressBar.hide()
|
||||
statusView.hide()
|
||||
binding.progressBar.hide()
|
||||
binding.statusView.hide()
|
||||
|
||||
initSwipeToRefresh()
|
||||
|
||||
|
@ -97,16 +100,16 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
|||
|
||||
private fun initSwipeToRefresh() {
|
||||
viewModel.refreshState.observe(viewLifecycleOwner) {
|
||||
swipeRefreshLayout.isRefreshing = it == NetworkState.LOADING
|
||||
binding.swipeRefreshLayout.isRefreshing = it == NetworkState.LOADING
|
||||
}
|
||||
swipeRefreshLayout.setOnRefreshListener {
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
viewModel.refresh()
|
||||
}
|
||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
}
|
||||
|
||||
private fun onTopLoaded() {
|
||||
recyclerView.scrollToPosition(0)
|
||||
binding.recyclerView.scrollToPosition(0)
|
||||
}
|
||||
|
||||
override fun onReblog(reblog: Boolean, position: Int) {
|
||||
|
@ -183,7 +186,7 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
|||
private fun jumpToTop() {
|
||||
if (isAdded) {
|
||||
layoutManager?.scrollToPosition(0)
|
||||
recyclerView.stopScroll()
|
||||
binding.recyclerView.stopScroll()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.databinding.ItemDraftBinding
|
||||
import com.keylesspalace.tusky.db.DraftEntity
|
||||
import com.keylesspalace.tusky.util.BindingViewHolder
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
|
@ -35,7 +35,7 @@ interface DraftActionListener {
|
|||
|
||||
class DraftsAdapter(
|
||||
private val listener: DraftActionListener
|
||||
) : PagedListAdapter<DraftEntity, BindingViewHolder<ItemDraftBinding>>(
|
||||
) : PagedListAdapter<DraftEntity, BindingHolder<ItemDraftBinding>>(
|
||||
object : DiffUtil.ItemCallback<DraftEntity>() {
|
||||
override fun areItemsTheSame(oldItem: DraftEntity, newItem: DraftEntity): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
|
@ -47,15 +47,15 @@ class DraftsAdapter(
|
|||
}
|
||||
) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder<ItemDraftBinding> {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemDraftBinding> {
|
||||
|
||||
val binding = ItemDraftBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
|
||||
val viewHolder = BindingViewHolder(binding)
|
||||
val viewHolder = BindingHolder(binding)
|
||||
|
||||
binding.draftMediaPreview.layoutManager = LinearLayoutManager(binding.root.context, RecyclerView.HORIZONTAL, false)
|
||||
binding.draftMediaPreview.adapter = DraftMediaAdapter {
|
||||
getItem(viewHolder.adapterPosition)?.let { draft ->
|
||||
getItem(viewHolder.bindingAdapterPosition)?.let { draft ->
|
||||
listener.onOpenDraft(draft)
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ class DraftsAdapter(
|
|||
return viewHolder
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: BindingViewHolder<ItemDraftBinding>, position: Int) {
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemDraftBinding>, position: Int) {
|
||||
getItem(position)?.let { draft ->
|
||||
holder.binding.root.setOnClickListener {
|
||||
listener.onOpenDraft(draft)
|
||||
|
|
|
@ -4,10 +4,10 @@ import android.os.Bundle
|
|||
import com.keylesspalace.tusky.BaseActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.instancemute.fragment.InstanceListFragment
|
||||
import com.keylesspalace.tusky.databinding.ActivityAccountListBinding
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import javax.inject.Inject
|
||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||
|
||||
class InstanceListActivity: BaseActivity(), HasAndroidInjector {
|
||||
|
||||
|
@ -16,9 +16,10 @@ class InstanceListActivity: BaseActivity(), HasAndroidInjector {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val binding = ActivityAccountListBinding.inflate(layoutInflater)
|
||||
setContentView(R.layout.activity_account_list)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
supportActionBar?.apply {
|
||||
setTitle(R.string.title_domain_mutes)
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
|
|
|
@ -1,22 +1,31 @@
|
|||
package com.keylesspalace.tusky.components.instancemute.adapter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.instancemute.interfaces.InstanceActionListener
|
||||
import kotlinx.android.synthetic.main.item_muted_domain.view.*
|
||||
import com.keylesspalace.tusky.databinding.ItemMutedDomainBinding
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
|
||||
class DomainMutesAdapter(
|
||||
private val actionListener: InstanceActionListener
|
||||
): RecyclerView.Adapter<BindingHolder<ItemMutedDomainBinding>>() {
|
||||
|
||||
class DomainMutesAdapter(private val actionListener: InstanceActionListener): RecyclerView.Adapter<DomainMutesAdapter.ViewHolder>() {
|
||||
var instances: MutableList<String> = mutableListOf()
|
||||
var bottomLoading: Boolean = false
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_muted_domain, parent, false), actionListener)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemMutedDomainBinding> {
|
||||
val binding = ItemMutedDomainBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return BindingHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.setupWithInstance(instances[position])
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemMutedDomainBinding>, position: Int) {
|
||||
val instance = instances[position]
|
||||
|
||||
holder.binding.mutedDomain.text = instance
|
||||
holder.binding.mutedDomainUnmute.setOnClickListener {
|
||||
actionListener.mute(false, instance, holder.bindingAdapterPosition)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
|
@ -37,21 +46,10 @@ class DomainMutesAdapter(private val actionListener: InstanceActionListener): Re
|
|||
notifyItemInserted(instances.size)
|
||||
}
|
||||
|
||||
fun removeItem(position: Int)
|
||||
{
|
||||
fun removeItem(position: Int) {
|
||||
if (position >= 0 && position < instances.size) {
|
||||
instances.removeAt(position)
|
||||
notifyItemRemoved(position)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ViewHolder(rootView: View, private val actionListener: InstanceActionListener): RecyclerView.ViewHolder(rootView) {
|
||||
fun setupWithInstance(instance: String) {
|
||||
itemView.muted_domain.text = instance
|
||||
itemView.muted_domain_unmute.setOnClickListener {
|
||||
actionListener.mute(false, instance, adapterPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,7 @@ package com.keylesspalace.tusky.components.instancemute.fragment
|
|||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
|
@ -14,16 +12,17 @@ import com.google.android.material.snackbar.Snackbar
|
|||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.instancemute.adapter.DomainMutesAdapter
|
||||
import com.keylesspalace.tusky.components.instancemute.interfaces.InstanceActionListener
|
||||
import com.keylesspalace.tusky.databinding.FragmentInstanceListBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.HttpHeaderLink
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener
|
||||
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
||||
import com.uber.autodispose.autoDispose
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.android.synthetic.main.fragment_instance_list.*
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
@ -31,9 +30,12 @@ import java.io.IOException
|
|||
import javax.inject.Inject
|
||||
|
||||
class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectable, InstanceActionListener {
|
||||
|
||||
@Inject
|
||||
lateinit var api: MastodonApi
|
||||
|
||||
private val binding by viewBinding(FragmentInstanceListBinding::bind)
|
||||
|
||||
private var fetching = false
|
||||
private var bottomId: String? = null
|
||||
private var adapter = DomainMutesAdapter(this)
|
||||
|
@ -42,12 +44,12 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
recyclerView.setHasFixedSize(true)
|
||||
recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
||||
recyclerView.adapter = adapter
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
val layoutManager = LinearLayoutManager(view.context)
|
||||
recyclerView.layoutManager = layoutManager
|
||||
binding.recyclerView.layoutManager = layoutManager
|
||||
|
||||
scrollListener = object : EndlessOnScrollListener(layoutManager) {
|
||||
override fun onLoadMore(totalItemsCount: Int, view: RecyclerView) {
|
||||
|
@ -57,7 +59,7 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
|||
}
|
||||
}
|
||||
|
||||
recyclerView.addOnScrollListener(scrollListener)
|
||||
binding.recyclerView.addOnScrollListener(scrollListener)
|
||||
fetchInstances()
|
||||
}
|
||||
|
||||
|
@ -85,7 +87,7 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
|||
override fun onResponse(call: Call<Any>, response: Response<Any>) {
|
||||
if (response.isSuccessful) {
|
||||
adapter.removeItem(position)
|
||||
Snackbar.make(recyclerView, getString(R.string.confirmation_domain_unmuted, instance), Snackbar.LENGTH_LONG)
|
||||
Snackbar.make(binding.recyclerView, getString(R.string.confirmation_domain_unmuted, instance), Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_undo) {
|
||||
mute(true, instance, position)
|
||||
}
|
||||
|
@ -103,10 +105,10 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
|||
return
|
||||
}
|
||||
fetching = true
|
||||
instanceProgressBar.show()
|
||||
binding.instanceProgressBar.show()
|
||||
|
||||
if (id != null) {
|
||||
recyclerView.post { adapter.bottomLoading = true }
|
||||
binding.recyclerView.post { adapter.bottomLoading = true }
|
||||
}
|
||||
|
||||
api.domainBlocks(id, bottomId)
|
||||
|
@ -116,7 +118,7 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
|||
val instances = response.body()
|
||||
|
||||
if (response.isSuccessful && instances != null) {
|
||||
onFetchInstancesSuccess(instances, response.headers().get("Link"))
|
||||
onFetchInstancesSuccess(instances, response.headers()["Link"])
|
||||
} else {
|
||||
onFetchInstancesFailure(Exception(response.message()))
|
||||
}
|
||||
|
@ -127,7 +129,7 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
|||
|
||||
private fun onFetchInstancesSuccess(instances: List<String>, linkHeader: String?) {
|
||||
adapter.bottomLoading = false
|
||||
instanceProgressBar.hide()
|
||||
binding.instanceProgressBar.hide()
|
||||
|
||||
val links = HttpHeaderLink.parse(linkHeader)
|
||||
val next = HttpHeaderLink.findByRelationType(links, "next")
|
||||
|
@ -137,32 +139,32 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
|||
fetching = false
|
||||
|
||||
if (adapter.itemCount == 0) {
|
||||
messageView.show()
|
||||
messageView.setup(
|
||||
binding.messageView.show()
|
||||
binding.messageView.setup(
|
||||
R.drawable.elephant_friend_empty,
|
||||
R.string.message_empty,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
messageView.hide()
|
||||
binding.messageView.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFetchInstancesFailure(throwable: Throwable) {
|
||||
fetching = false
|
||||
instanceProgressBar.hide()
|
||||
binding.instanceProgressBar.hide()
|
||||
Log.e(TAG, "Fetch failure", throwable)
|
||||
|
||||
if (adapter.itemCount == 0) {
|
||||
messageView.show()
|
||||
binding.messageView.show()
|
||||
if (throwable is IOException) {
|
||||
messageView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||
messageView.hide()
|
||||
binding.messageView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||
binding.messageView.hide()
|
||||
this.fetchInstances(null)
|
||||
}
|
||||
} else {
|
||||
messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||
messageView.hide()
|
||||
binding.messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||
binding.messageView.hide()
|
||||
this.fetchInstances(null)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -242,7 +242,7 @@ public class NotificationHelper {
|
|||
|
||||
if (currentNotifications.length() != 1) {
|
||||
try {
|
||||
String title = context.getString(R.string.notification_title_summary, currentNotifications.length());
|
||||
String title = context.getResources().getQuantityString(R.plurals.notification_title_summary, currentNotifications.length(), currentNotifications.length());
|
||||
String text = joinNames(context, currentNotifications);
|
||||
summaryBuilder.setContentTitle(title)
|
||||
.setContentText(text);
|
||||
|
|
|
@ -8,14 +8,23 @@ import android.os.Build
|
|||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import android.widget.RadioButton
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.SplashActivity
|
||||
import com.keylesspalace.tusky.databinding.DialogEmojicompatBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemEmojiPrefBinding
|
||||
import com.keylesspalace.tusky.util.EmojiCompatFont
|
||||
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.BLOBMOJI
|
||||
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.FONTS
|
||||
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.NOTOEMOJI
|
||||
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.SYSTEM_DEFAULT
|
||||
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.TWEMOJI
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import okhttp3.OkHttpClient
|
||||
|
@ -50,94 +59,85 @@ class EmojiPreference(
|
|||
}
|
||||
|
||||
override fun onClick() {
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.dialog_emojicompat, null)
|
||||
viewIds.forEachIndexed { index, viewId ->
|
||||
setupItem(view.findViewById(viewId), FONTS[index])
|
||||
}
|
||||
val binding = DialogEmojicompatBinding.inflate(LayoutInflater.from(context))
|
||||
|
||||
setupItem(BLOBMOJI, binding.itemBlobmoji)
|
||||
setupItem(TWEMOJI, binding.itemTwemoji)
|
||||
setupItem(NOTOEMOJI, binding.itemNotoemoji)
|
||||
setupItem(SYSTEM_DEFAULT, binding.itemNomoji)
|
||||
|
||||
AlertDialog.Builder(context)
|
||||
.setView(view)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> onDialogOk() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun setupItem(container: View, font: EmojiCompatFont) {
|
||||
val title: TextView = container.findViewById(R.id.emojicompat_name)
|
||||
val caption: TextView = container.findViewById(R.id.emojicompat_caption)
|
||||
val thumb: ImageView = container.findViewById(R.id.emojicompat_thumb)
|
||||
val download: ImageButton = container.findViewById(R.id.emojicompat_download)
|
||||
val cancel: ImageButton = container.findViewById(R.id.emojicompat_download_cancel)
|
||||
val radio: RadioButton = container.findViewById(R.id.emojicompat_radio)
|
||||
|
||||
private fun setupItem(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
|
||||
// Initialize all the views
|
||||
title.text = font.getDisplay(container.context)
|
||||
caption.setText(font.caption)
|
||||
thumb.setImageResource(font.img)
|
||||
binding.emojiName.text = font.getDisplay(context)
|
||||
binding.emojiCaption.setText(font.caption)
|
||||
binding.emojiThumbnail.setImageResource(font.img)
|
||||
|
||||
// There needs to be a list of all the radio buttons in order to uncheck them when one is selected
|
||||
radioButtons.add(radio)
|
||||
updateItem(font, container)
|
||||
radioButtons.add(binding.emojiRadioButton)
|
||||
updateItem(font, binding)
|
||||
|
||||
// Set actions
|
||||
download.setOnClickListener { startDownload(font, container) }
|
||||
cancel.setOnClickListener { cancelDownload(font, container) }
|
||||
radio.setOnClickListener { radioButton: View -> select(font, radioButton as RadioButton) }
|
||||
container.setOnClickListener { containerView: View ->
|
||||
select(font, containerView.findViewById(R.id.emojicompat_radio))
|
||||
binding.emojiDownload.setOnClickListener { startDownload(font, binding) }
|
||||
binding.emojiDownloadCancel.setOnClickListener { cancelDownload(font, binding) }
|
||||
binding.emojiRadioButton.setOnClickListener { radioButton: View -> select(font, radioButton as RadioButton) }
|
||||
binding.root.setOnClickListener {
|
||||
select(font, binding.emojiRadioButton)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startDownload(font: EmojiCompatFont, container: View) {
|
||||
val download: ImageButton = container.findViewById(R.id.emojicompat_download)
|
||||
val caption: TextView = container.findViewById(R.id.emojicompat_caption)
|
||||
val progressBar: ProgressBar = container.findViewById(R.id.emojicompat_progress)
|
||||
val cancel: ImageButton = container.findViewById(R.id.emojicompat_download_cancel)
|
||||
|
||||
private fun startDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
|
||||
// Switch to downloading style
|
||||
download.visibility = View.GONE
|
||||
caption.visibility = View.INVISIBLE
|
||||
progressBar.visibility = View.VISIBLE
|
||||
progressBar.progress = 0
|
||||
cancel.visibility = View.VISIBLE
|
||||
binding.emojiDownload.hide()
|
||||
binding.emojiCaption.visibility = View.INVISIBLE
|
||||
binding.emojiProgress.show()
|
||||
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) {
|
||||
progressBar.isIndeterminate = false
|
||||
val max = progressBar.max.toFloat()
|
||||
binding.emojiProgress.isIndeterminate = false
|
||||
val max = binding.emojiProgress.max.toFloat()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
progressBar.setProgress((max * progress).toInt(), true)
|
||||
binding.emojiProgress.setProgress((max * progress).toInt(), true)
|
||||
} else {
|
||||
progressBar.progress = (max * progress).toInt()
|
||||
binding.emojiProgress.progress = (max * progress).toInt()
|
||||
}
|
||||
} else {
|
||||
progressBar.isIndeterminate = true
|
||||
binding.emojiProgress.isIndeterminate = true
|
||||
}
|
||||
},
|
||||
{
|
||||
Toast.makeText(context, R.string.download_failed, Toast.LENGTH_SHORT).show()
|
||||
updateItem(font, container)
|
||||
updateItem(font, binding)
|
||||
},
|
||||
{
|
||||
finishDownload(font, container)
|
||||
finishDownload(font, binding)
|
||||
}
|
||||
).also { downloadDisposables[font.id] = it }
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun cancelDownload(font: EmojiCompatFont, container: View) {
|
||||
font.deleteDownloadedFile(container.context)
|
||||
private fun cancelDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
|
||||
font.deleteDownloadedFile(context)
|
||||
downloadDisposables[font.id]?.dispose()
|
||||
downloadDisposables[font.id] = null
|
||||
updateItem(font, container)
|
||||
updateItem(font, binding)
|
||||
}
|
||||
|
||||
private fun finishDownload(font: EmojiCompatFont, container: View) {
|
||||
select(font, container.findViewById(R.id.emojicompat_radio))
|
||||
updateItem(font, container)
|
||||
private fun finishDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
|
||||
select(font, binding.emojiRadioButton)
|
||||
updateItem(font, binding)
|
||||
// Set the flag to restart the app (because an update has been downloaded)
|
||||
if (selected === original && currentNeedsUpdate) {
|
||||
updated = true
|
||||
|
@ -153,54 +153,43 @@ class EmojiPreference(
|
|||
*/
|
||||
private fun select(font: EmojiCompatFont, radio: RadioButton) {
|
||||
selected = font
|
||||
// Uncheck all the other buttons
|
||||
for (other in radioButtons) {
|
||||
if (other !== radio) {
|
||||
other.isChecked = false
|
||||
}
|
||||
radioButtons.forEach { radioButton ->
|
||||
radioButton.isChecked = radioButton == radio
|
||||
}
|
||||
radio.isChecked = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a "consistent" state is reached, i.e. it's not downloading the font
|
||||
*
|
||||
* @param font The font to be displayed
|
||||
* @param container The ConstraintLayout containing the item
|
||||
* @param binding The ItemEmojiPrefBinding to show the item in
|
||||
*/
|
||||
private fun updateItem(font: EmojiCompatFont, container: View) {
|
||||
// Assignments
|
||||
val download: ImageButton = container.findViewById(R.id.emojicompat_download)
|
||||
val caption: TextView = container.findViewById(R.id.emojicompat_caption)
|
||||
val progress: ProgressBar = container.findViewById(R.id.emojicompat_progress)
|
||||
val cancel: ImageButton = container.findViewById(R.id.emojicompat_download_cancel)
|
||||
val radio: RadioButton = container.findViewById(R.id.emojicompat_radio)
|
||||
|
||||
private fun updateItem(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
|
||||
// There's no download going on
|
||||
progress.visibility = View.GONE
|
||||
cancel.visibility = View.GONE
|
||||
caption.visibility = View.VISIBLE
|
||||
binding.emojiProgress.hide()
|
||||
binding.emojiDownloadCancel.hide()
|
||||
binding.emojiCaption.show()
|
||||
if (font.isDownloaded(context)) {
|
||||
// Make it selectable
|
||||
download.visibility = View.GONE
|
||||
radio.visibility = View.VISIBLE
|
||||
container.isClickable = true
|
||||
binding.emojiDownload.hide()
|
||||
binding.emojiRadioButton.show()
|
||||
binding.root.isClickable = true
|
||||
} else {
|
||||
// Make it downloadable
|
||||
download.visibility = View.VISIBLE
|
||||
radio.visibility = View.GONE
|
||||
container.isClickable = false
|
||||
binding.emojiDownload.show()
|
||||
binding.emojiRadioButton.hide()
|
||||
binding.root.isClickable = false
|
||||
}
|
||||
|
||||
// Select it if necessary
|
||||
if (font === selected) {
|
||||
radio.isChecked = true
|
||||
binding.emojiRadioButton.isChecked = true
|
||||
// Update available
|
||||
if (!font.isDownloaded(context)) {
|
||||
currentNeedsUpdate = true
|
||||
}
|
||||
} else {
|
||||
radio.isChecked = false
|
||||
binding.emojiRadioButton.isChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,13 +235,5 @@ class EmojiPreference(
|
|||
|
||||
companion object {
|
||||
private const val TAG = "EmojiPreference"
|
||||
|
||||
// Please note that this array must sorted in the same way as the fonts.
|
||||
private val viewIds = intArrayOf(
|
||||
R.id.item_nomoji,
|
||||
R.id.item_blobmoji,
|
||||
R.id.item_twemoji,
|
||||
R.id.item_notoemoji
|
||||
)
|
||||
}
|
||||
}
|
|
@ -28,12 +28,12 @@ import com.keylesspalace.tusky.MainActivity
|
|||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||
import com.keylesspalace.tusky.databinding.ActivityPreferencesBinding
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.getNonNullString
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
|
@ -48,12 +48,12 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference
|
|||
private var restartActivitiesOnExit: Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_preferences)
|
||||
val binding = ActivityPreferencesBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
supportActionBar?.run {
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowHomeEnabled(true)
|
||||
|
|
|
@ -22,11 +22,11 @@ import androidx.activity.viewModels
|
|||
import com.keylesspalace.tusky.BottomSheetActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.report.adapter.ReportPagerAdapter
|
||||
import com.keylesspalace.tusky.databinding.ActivityReportBinding
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import kotlinx.android.synthetic.main.activity_report.*
|
||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
|
||||
|
@ -39,6 +39,8 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
|
||||
private val viewModel: ReportViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private val binding by viewBinding(ActivityReportBinding::inflate)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val accountId = intent?.getStringExtra(ACCOUNT_ID)
|
||||
|
@ -50,9 +52,9 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
viewModel.init(accountId, accountUserName, intent?.getStringExtra(STATUS_ID))
|
||||
|
||||
|
||||
setContentView(R.layout.activity_report)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
|
||||
supportActionBar?.apply {
|
||||
title = getString(R.string.report_username_format, viewModel.accountUserName)
|
||||
|
@ -69,8 +71,8 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
}
|
||||
|
||||
private fun initViewPager() {
|
||||
wizard.isUserInputEnabled = false
|
||||
wizard.adapter = ReportPagerAdapter(this)
|
||||
binding.wizard.isUserInputEnabled = false
|
||||
binding.wizard.adapter = ReportPagerAdapter(this)
|
||||
}
|
||||
|
||||
private fun subscribeObservables() {
|
||||
|
@ -96,18 +98,18 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
}
|
||||
|
||||
private fun showPreviousScreen() {
|
||||
when (wizard.currentItem) {
|
||||
when (binding.wizard.currentItem) {
|
||||
0 -> closeScreen()
|
||||
1 -> showStatusesPage()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDonePage() {
|
||||
wizard.currentItem = 2
|
||||
binding.wizard.currentItem = 2
|
||||
}
|
||||
|
||||
private fun showNotesPage() {
|
||||
wizard.currentItem = 1
|
||||
binding.wizard.currentItem = 1
|
||||
}
|
||||
|
||||
private fun closeScreen() {
|
||||
|
@ -115,7 +117,7 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
}
|
||||
|
||||
private fun showStatusesPage() {
|
||||
wizard.currentItem = 0
|
||||
binding.wizard.currentItem = 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.view.View
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.report.model.StatusViewState
|
||||
import com.keylesspalace.tusky.databinding.ItemReportStatusBinding
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
|
@ -28,16 +29,15 @@ import com.keylesspalace.tusky.util.*
|
|||
import com.keylesspalace.tusky.util.StatusViewHelper.Companion.COLLAPSE_INPUT_FILTER
|
||||
import com.keylesspalace.tusky.util.StatusViewHelper.Companion.NO_INPUT_FILTER
|
||||
import com.keylesspalace.tusky.viewdata.toViewData
|
||||
import kotlinx.android.synthetic.main.item_report_status.view.*
|
||||
import java.util.*
|
||||
|
||||
class StatusViewHolder(
|
||||
itemView: View,
|
||||
private val binding: ItemReportStatusBinding,
|
||||
private val statusDisplayOptions: StatusDisplayOptions,
|
||||
private val viewState: StatusViewState,
|
||||
private val adapterHandler: AdapterHandler,
|
||||
private val getStatusForPosition: (Int) -> Status?
|
||||
) : RecyclerView.ViewHolder(itemView) {
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
private val mediaViewHeight = itemView.context.resources.getDimensionPixelSize(R.dimen.status_media_preview_height)
|
||||
private val statusViewHelper = StatusViewHelper(itemView)
|
||||
|
||||
|
@ -56,16 +56,16 @@ class StatusViewHolder(
|
|||
}
|
||||
|
||||
init {
|
||||
itemView.statusSelection.setOnCheckedChangeListener { _, isChecked ->
|
||||
binding.statusSelection.setOnCheckedChangeListener { _, isChecked ->
|
||||
status()?.let { status ->
|
||||
adapterHandler.setStatusChecked(status, isChecked)
|
||||
}
|
||||
}
|
||||
itemView.status_media_preview_container.clipToOutline = true
|
||||
binding.statusMediaPreviewContainer.clipToOutline = true
|
||||
}
|
||||
|
||||
fun bind(status: Status) {
|
||||
itemView.statusSelection.isChecked = adapterHandler.isStatusChecked(status.id)
|
||||
binding.statusSelection.isChecked = adapterHandler.isStatusChecked(status.id)
|
||||
|
||||
updateTextView()
|
||||
|
||||
|
@ -86,18 +86,18 @@ class StatusViewHolder(
|
|||
|
||||
if (status.spoilerText.isBlank()) {
|
||||
setTextVisible(true, status.content, status.mentions, status.emojis, adapterHandler)
|
||||
itemView.statusContentWarningButton.hide()
|
||||
itemView.statusContentWarningDescription.hide()
|
||||
binding.statusContentWarningButton.hide()
|
||||
binding.statusContentWarningDescription.hide()
|
||||
} else {
|
||||
val emojiSpoiler = status.spoilerText.emojify(status.emojis, itemView.statusContentWarningDescription, statusDisplayOptions.animateEmojis)
|
||||
itemView.statusContentWarningDescription.text = emojiSpoiler
|
||||
itemView.statusContentWarningDescription.show()
|
||||
itemView.statusContentWarningButton.show()
|
||||
val emojiSpoiler = status.spoilerText.emojify(status.emojis, binding.statusContentWarningDescription, statusDisplayOptions.animateEmojis)
|
||||
binding.statusContentWarningDescription.text = emojiSpoiler
|
||||
binding.statusContentWarningDescription.show()
|
||||
binding.statusContentWarningButton.show()
|
||||
setContentWarningButtonText(viewState.isContentShow(status.id, true))
|
||||
itemView.statusContentWarningButton.setOnClickListener {
|
||||
binding.statusContentWarningButton.setOnClickListener {
|
||||
status()?.let { status ->
|
||||
val contentShown = viewState.isContentShow(status.id, true)
|
||||
itemView.statusContentWarningDescription.invalidate()
|
||||
binding.statusContentWarningDescription.invalidate()
|
||||
viewState.setContentShow(status.id, !contentShown)
|
||||
setTextVisible(!contentShown, status.content, status.mentions, status.emojis, adapterHandler)
|
||||
setContentWarningButtonText(!contentShown)
|
||||
|
@ -110,9 +110,9 @@ class StatusViewHolder(
|
|||
|
||||
private fun setContentWarningButtonText(contentShown: Boolean) {
|
||||
if(contentShown) {
|
||||
itemView.statusContentWarningButton.setText(R.string.status_content_warning_show_less)
|
||||
binding.statusContentWarningButton.setText(R.string.status_content_warning_show_less)
|
||||
} else {
|
||||
itemView.statusContentWarningButton.setText(R.string.status_content_warning_show_more)
|
||||
binding.statusContentWarningButton.setText(R.string.status_content_warning_show_more)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,26 +122,26 @@ class StatusViewHolder(
|
|||
emojis: List<Emoji>,
|
||||
listener: LinkListener) {
|
||||
if (expanded) {
|
||||
val emojifiedText = content.emojify(emojis, itemView.statusContent, statusDisplayOptions.animateEmojis)
|
||||
LinkHelper.setClickableText(itemView.statusContent, emojifiedText, mentions, listener)
|
||||
val emojifiedText = content.emojify(emojis, binding.statusContent, statusDisplayOptions.animateEmojis)
|
||||
LinkHelper.setClickableText(binding.statusContent, emojifiedText, mentions, listener)
|
||||
} else {
|
||||
LinkHelper.setClickableMentions(itemView.statusContent, mentions, listener)
|
||||
LinkHelper.setClickableMentions(binding.statusContent, mentions, listener)
|
||||
}
|
||||
if (itemView.statusContent.text.isNullOrBlank()) {
|
||||
itemView.statusContent.hide()
|
||||
if (binding.statusContent.text.isNullOrBlank()) {
|
||||
binding.statusContent.hide()
|
||||
} else {
|
||||
itemView.statusContent.show()
|
||||
binding.statusContent.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setCreatedAt(createdAt: Date?) {
|
||||
if (statusDisplayOptions.useAbsoluteTime) {
|
||||
itemView.timestampInfo.text = statusViewHelper.getAbsoluteTime(createdAt)
|
||||
binding.timestampInfo.text = statusViewHelper.getAbsoluteTime(createdAt)
|
||||
} else {
|
||||
itemView.timestampInfo.text = if (createdAt != null) {
|
||||
binding.timestampInfo.text = if (createdAt != null) {
|
||||
val then = createdAt.time
|
||||
val now = System.currentTimeMillis()
|
||||
TimestampUtils.getRelativeTimeSpanString(itemView.timestampInfo.context, then, now)
|
||||
TimestampUtils.getRelativeTimeSpanString(binding.timestampInfo.context, then, now)
|
||||
} else {
|
||||
// unknown minutes~
|
||||
"?m"
|
||||
|
@ -149,30 +149,29 @@ 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))) {
|
||||
itemView.buttonToggleContent.setOnClickListener{
|
||||
binding.buttonToggleContent.setOnClickListener{
|
||||
status()?.let { status ->
|
||||
viewState.setCollapsed(status.id, !collapsed)
|
||||
updateTextView()
|
||||
}
|
||||
}
|
||||
|
||||
itemView.buttonToggleContent.show()
|
||||
binding.buttonToggleContent.show()
|
||||
if (collapsed) {
|
||||
itemView.buttonToggleContent.setText(R.string.status_content_show_more)
|
||||
itemView.statusContent.filters = COLLAPSE_INPUT_FILTER
|
||||
binding.buttonToggleContent.setText(R.string.status_content_show_more)
|
||||
binding.statusContent.filters = COLLAPSE_INPUT_FILTER
|
||||
} else {
|
||||
itemView.buttonToggleContent.setText(R.string.status_content_show_less)
|
||||
itemView.statusContent.filters = NO_INPUT_FILTER
|
||||
binding.buttonToggleContent.setText(R.string.status_content_show_less)
|
||||
binding.statusContent.filters = NO_INPUT_FILTER
|
||||
}
|
||||
} else {
|
||||
itemView.buttonToggleContent.hide()
|
||||
itemView.statusContent.filters = NO_INPUT_FILTER
|
||||
binding.buttonToggleContent.hide()
|
||||
binding.statusContent.filters = NO_INPUT_FILTER
|
||||
}
|
||||
}
|
||||
|
||||
private fun status() = getStatusForPosition(adapterPosition)
|
||||
private fun status() = getStatusForPosition(bindingAdapterPosition)
|
||||
}
|
|
@ -20,8 +20,8 @@ import android.view.ViewGroup
|
|||
import androidx.paging.PagedListAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.report.model.StatusViewState
|
||||
import com.keylesspalace.tusky.databinding.ItemReportStatusBinding
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
|
||||
|
@ -29,29 +29,25 @@ class StatusesAdapter(
|
|||
private val statusDisplayOptions: StatusDisplayOptions,
|
||||
private val statusViewState: StatusViewState,
|
||||
private val adapterHandler: AdapterHandler
|
||||
) : PagedListAdapter<Status, RecyclerView.ViewHolder>(STATUS_COMPARATOR) {
|
||||
) : PagedListAdapter<Status, StatusViewHolder>(STATUS_COMPARATOR) {
|
||||
|
||||
private val statusForPosition: (Int) -> Status? = { position: Int ->
|
||||
if (position != RecyclerView.NO_POSITION) getItem(position) else null
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_report_status, parent, false)
|
||||
return StatusViewHolder(view, statusDisplayOptions, statusViewState, adapterHandler,
|
||||
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)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
override fun onBindViewHolder(holder: StatusViewHolder, position: Int) {
|
||||
getItem(position)?.let { status ->
|
||||
(holder as? StatusViewHolder)?.bind(status)
|
||||
holder.bind(status)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val STATUS_COMPARATOR = object : DiffUtil.ItemCallback<Status>() {
|
||||
override fun areContentsTheSame(oldItem: Status, newItem: Status): Boolean =
|
||||
oldItem == newItem
|
||||
|
@ -59,7 +55,5 @@ class StatusesAdapter(
|
|||
override fun areItemsTheSame(oldItem: Status, newItem: Status): Boolean =
|
||||
oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,12 +22,13 @@ import androidx.fragment.app.activityViewModels
|
|||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.report.ReportViewModel
|
||||
import com.keylesspalace.tusky.components.report.Screen
|
||||
import com.keylesspalace.tusky.databinding.FragmentReportDoneBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.util.Loading
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import kotlinx.android.synthetic.main.fragment_report_done.*
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import javax.inject.Inject
|
||||
|
||||
class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
||||
|
@ -37,8 +38,10 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
|||
|
||||
private val viewModel: ReportViewModel by activityViewModels { viewModelFactory }
|
||||
|
||||
private val binding by viewBinding(FragmentReportDoneBinding::bind)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
textReported.text = getString(R.string.report_sent_success, viewModel.accountUserName)
|
||||
binding.textReported.text = getString(R.string.report_sent_success, viewModel.accountUserName)
|
||||
handleClicks()
|
||||
subscribeObservables()
|
||||
}
|
||||
|
@ -46,14 +49,14 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
|||
private fun subscribeObservables() {
|
||||
viewModel.muteState.observe(viewLifecycleOwner) {
|
||||
if (it !is Loading) {
|
||||
buttonMute.show()
|
||||
progressMute.show()
|
||||
binding.buttonMute.show()
|
||||
binding.progressMute.show()
|
||||
} else {
|
||||
buttonMute.hide()
|
||||
progressMute.hide()
|
||||
binding.buttonMute.hide()
|
||||
binding.progressMute.hide()
|
||||
}
|
||||
|
||||
buttonMute.setText(when (it.data) {
|
||||
binding.buttonMute.setText(when (it.data) {
|
||||
true -> R.string.action_unmute
|
||||
else -> R.string.action_mute
|
||||
})
|
||||
|
@ -61,14 +64,14 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
|||
|
||||
viewModel.blockState.observe(viewLifecycleOwner) {
|
||||
if (it !is Loading) {
|
||||
buttonBlock.show()
|
||||
progressBlock.show()
|
||||
binding.buttonBlock.show()
|
||||
binding.progressBlock.show()
|
||||
}
|
||||
else{
|
||||
buttonBlock.hide()
|
||||
progressBlock.hide()
|
||||
else {
|
||||
binding.buttonBlock.hide()
|
||||
binding.progressBlock.hide()
|
||||
}
|
||||
buttonBlock.setText(when (it.data) {
|
||||
binding.buttonBlock.setText(when (it.data) {
|
||||
true -> R.string.action_unblock
|
||||
else -> R.string.action_block
|
||||
})
|
||||
|
@ -77,13 +80,13 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
|||
}
|
||||
|
||||
private fun handleClicks() {
|
||||
buttonDone.setOnClickListener {
|
||||
binding.buttonDone.setOnClickListener {
|
||||
viewModel.navigateTo(Screen.Finish)
|
||||
}
|
||||
buttonBlock.setOnClickListener {
|
||||
binding.buttonBlock.setOnClickListener {
|
||||
viewModel.toggleBlock()
|
||||
}
|
||||
buttonMute.setOnClickListener {
|
||||
binding.buttonMute.setOnClickListener {
|
||||
viewModel.toggleMute()
|
||||
}
|
||||
}
|
||||
|
@ -91,5 +94,4 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
|||
companion object {
|
||||
fun newInstance() = ReportDoneFragment()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,10 +24,10 @@ import com.google.android.material.snackbar.Snackbar
|
|||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.report.ReportViewModel
|
||||
import com.keylesspalace.tusky.components.report.Screen
|
||||
import com.keylesspalace.tusky.databinding.FragmentReportNoteBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import kotlinx.android.synthetic.main.fragment_report_note.*
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -38,6 +38,8 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
|||
|
||||
private val viewModel: ReportViewModel by activityViewModels { viewModelFactory }
|
||||
|
||||
private val binding by viewBinding(FragmentReportNoteBinding::bind)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
fillViews()
|
||||
handleChanges()
|
||||
|
@ -46,29 +48,29 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
|||
}
|
||||
|
||||
private fun handleChanges() {
|
||||
editNote.doAfterTextChanged {
|
||||
binding.editNote.doAfterTextChanged {
|
||||
viewModel.reportNote = it?.toString() ?: ""
|
||||
}
|
||||
checkIsNotifyRemote.setOnCheckedChangeListener { _, isChecked ->
|
||||
binding.checkIsNotifyRemote.setOnCheckedChangeListener { _, isChecked ->
|
||||
viewModel.isRemoteNotify = isChecked
|
||||
}
|
||||
}
|
||||
|
||||
private fun fillViews() {
|
||||
editNote.setText(viewModel.reportNote)
|
||||
binding.editNote.setText(viewModel.reportNote)
|
||||
|
||||
if (viewModel.isRemoteAccount){
|
||||
checkIsNotifyRemote.show()
|
||||
reportDescriptionRemoteInstance.show()
|
||||
binding.checkIsNotifyRemote.show()
|
||||
binding.reportDescriptionRemoteInstance.show()
|
||||
}
|
||||
else{
|
||||
checkIsNotifyRemote.hide()
|
||||
reportDescriptionRemoteInstance.hide()
|
||||
binding.checkIsNotifyRemote.hide()
|
||||
binding.reportDescriptionRemoteInstance.hide()
|
||||
}
|
||||
|
||||
if (viewModel.isRemoteAccount)
|
||||
checkIsNotifyRemote.text = getString(R.string.report_remote_instance, viewModel.remoteServer)
|
||||
checkIsNotifyRemote.isChecked = viewModel.isRemoteNotify
|
||||
binding.checkIsNotifyRemote.text = getString(R.string.report_remote_instance, viewModel.remoteServer)
|
||||
binding.checkIsNotifyRemote.isChecked = viewModel.isRemoteNotify
|
||||
}
|
||||
|
||||
private fun subscribeObservables() {
|
||||
|
@ -83,13 +85,13 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
|||
}
|
||||
|
||||
private fun showError(error: Throwable?) {
|
||||
editNote.isEnabled = true
|
||||
checkIsNotifyRemote.isEnabled = true
|
||||
buttonReport.isEnabled = true
|
||||
buttonBack.isEnabled = true
|
||||
progressBar.hide()
|
||||
binding.editNote.isEnabled = true
|
||||
binding.checkIsNotifyRemote.isEnabled = true
|
||||
binding.buttonReport.isEnabled = true
|
||||
binding.buttonBack.isEnabled = true
|
||||
binding.progressBar.hide()
|
||||
|
||||
Snackbar.make(buttonBack, if (error is IOException) R.string.error_network else R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
Snackbar.make(binding.buttonBack, if (error is IOException) R.string.error_network else R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.apply {
|
||||
setAction(R.string.action_retry) {
|
||||
sendReport()
|
||||
|
@ -103,19 +105,19 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
|||
}
|
||||
|
||||
private fun showLoading() {
|
||||
buttonReport.isEnabled = false
|
||||
buttonBack.isEnabled = false
|
||||
editNote.isEnabled = false
|
||||
checkIsNotifyRemote.isEnabled = false
|
||||
progressBar.show()
|
||||
binding.buttonReport.isEnabled = false
|
||||
binding.buttonBack.isEnabled = false
|
||||
binding.editNote.isEnabled = false
|
||||
binding.checkIsNotifyRemote.isEnabled = false
|
||||
binding.progressBar.show()
|
||||
}
|
||||
|
||||
private fun handleClicks() {
|
||||
buttonBack.setOnClickListener {
|
||||
binding.buttonBack.setOnClickListener {
|
||||
viewModel.navigateTo(Screen.Back)
|
||||
}
|
||||
|
||||
buttonReport.setOnClickListener {
|
||||
binding.buttonReport.setOnClickListener {
|
||||
sendReport()
|
||||
}
|
||||
}
|
||||
|
@ -123,5 +125,4 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
|||
companion object {
|
||||
fun newInstance() = ReportNoteFragment()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import com.keylesspalace.tusky.components.report.ReportViewModel
|
|||
import com.keylesspalace.tusky.components.report.Screen
|
||||
import com.keylesspalace.tusky.components.report.adapter.AdapterHandler
|
||||
import com.keylesspalace.tusky.components.report.adapter.StatusesAdapter
|
||||
import com.keylesspalace.tusky.databinding.FragmentReportStatusesBinding
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
|
@ -44,8 +45,8 @@ import com.keylesspalace.tusky.util.CardViewMode
|
|||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
||||
import kotlinx.android.synthetic.main.fragment_report_statuses.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Injectable, AdapterHandler {
|
||||
|
@ -58,6 +59,8 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
|||
|
||||
private val viewModel: ReportViewModel by activityViewModels { viewModelFactory }
|
||||
|
||||
private val binding by viewBinding(FragmentReportStatusesBinding::bind)
|
||||
|
||||
private lateinit var adapter: StatusesAdapter
|
||||
|
||||
private var snackbarErrorRetry: Snackbar? = null
|
||||
|
@ -93,9 +96,9 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
|||
}
|
||||
|
||||
private fun setupSwipeRefreshLayout() {
|
||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
|
||||
swipeRefreshLayout.setOnRefreshListener {
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
snackbarErrorRetry?.dismiss()
|
||||
viewModel.refreshStatuses()
|
||||
}
|
||||
|
@ -118,10 +121,10 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
|||
adapter = StatusesAdapter(statusDisplayOptions,
|
||||
viewModel.statusViewState, this)
|
||||
|
||||
recyclerView.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
|
||||
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
recyclerView.adapter = adapter
|
||||
(recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
binding.recyclerView.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
binding.recyclerView.adapter = adapter
|
||||
(binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
|
||||
viewModel.statuses.observe(viewLifecycleOwner) {
|
||||
adapter.submitList(it)
|
||||
|
@ -129,9 +132,9 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
|||
|
||||
viewModel.networkStateAfter.observe(viewLifecycleOwner) {
|
||||
if (it?.status == com.keylesspalace.tusky.util.Status.RUNNING)
|
||||
progressBarBottom.show()
|
||||
binding.progressBarBottom.show()
|
||||
else
|
||||
progressBarBottom.hide()
|
||||
binding.progressBarBottom.hide()
|
||||
|
||||
if (it?.status == com.keylesspalace.tusky.util.Status.FAILED)
|
||||
showError(it.msg)
|
||||
|
@ -139,22 +142,22 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
|||
|
||||
viewModel.networkStateBefore.observe(viewLifecycleOwner) {
|
||||
if (it?.status == com.keylesspalace.tusky.util.Status.RUNNING)
|
||||
progressBarTop.show()
|
||||
binding.progressBarTop.show()
|
||||
else
|
||||
progressBarTop.hide()
|
||||
binding.progressBarTop.hide()
|
||||
|
||||
if (it?.status == com.keylesspalace.tusky.util.Status.FAILED)
|
||||
showError(it.msg)
|
||||
}
|
||||
|
||||
viewModel.networkStateRefresh.observe(viewLifecycleOwner) {
|
||||
if (it?.status == com.keylesspalace.tusky.util.Status.RUNNING && !swipeRefreshLayout.isRefreshing)
|
||||
progressBarLoading.show()
|
||||
if (it?.status == com.keylesspalace.tusky.util.Status.RUNNING && !binding.swipeRefreshLayout.isRefreshing)
|
||||
binding.progressBarLoading.show()
|
||||
else
|
||||
progressBarLoading.hide()
|
||||
binding.progressBarLoading.hide()
|
||||
|
||||
if (it?.status != com.keylesspalace.tusky.util.Status.RUNNING)
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
if (it?.status == com.keylesspalace.tusky.util.Status.FAILED)
|
||||
showError(it.msg)
|
||||
}
|
||||
|
@ -162,7 +165,7 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
|||
|
||||
private fun showError(@Suppress("UNUSED_PARAMETER") msg: String?) {
|
||||
if (snackbarErrorRetry?.isShown != true) {
|
||||
snackbarErrorRetry = Snackbar.make(swipeRefreshLayout, R.string.failed_fetch_statuses, Snackbar.LENGTH_INDEFINITE)
|
||||
snackbarErrorRetry = Snackbar.make(binding.swipeRefreshLayout, R.string.failed_fetch_statuses, Snackbar.LENGTH_INDEFINITE)
|
||||
snackbarErrorRetry?.setAction(R.string.action_retry) {
|
||||
viewModel.retryStatusLoad()
|
||||
}
|
||||
|
@ -172,11 +175,11 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
|||
|
||||
|
||||
private fun handleClicks() {
|
||||
buttonCancel.setOnClickListener {
|
||||
binding.buttonCancel.setOnClickListener {
|
||||
viewModel.navigateTo(Screen.Back)
|
||||
}
|
||||
|
||||
buttonContinue.setOnClickListener {
|
||||
binding.buttonContinue.setOnClickListener {
|
||||
viewModel.navigateTo(Screen.Note)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,20 +18,19 @@ package com.keylesspalace.tusky.components.scheduled
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.activity.viewModels
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.keylesspalace.tusky.BaseActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.compose.ComposeActivity
|
||||
import com.keylesspalace.tusky.databinding.ActivityScheduledTootBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.entity.ScheduledStatus
|
||||
import com.keylesspalace.tusky.util.Status
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import kotlinx.android.synthetic.main.activity_scheduled_toot.*
|
||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class ScheduledTootActivity : BaseActivity(), ScheduledTootActionListener, Injectable {
|
||||
|
@ -39,31 +38,31 @@ class ScheduledTootActivity : BaseActivity(), ScheduledTootActionListener, Injec
|
|||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
lateinit var viewModel: ScheduledTootViewModel
|
||||
private val viewModel: ScheduledTootViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private val adapter = ScheduledTootAdapter(this)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_scheduled_toot)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
val binding = ActivityScheduledTootBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
supportActionBar?.run {
|
||||
title = getString(R.string.title_scheduled_toot)
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowHomeEnabled(true)
|
||||
}
|
||||
|
||||
swipeRefreshLayout.setOnRefreshListener(this::refreshStatuses)
|
||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
binding.swipeRefreshLayout.setOnRefreshListener(this::refreshStatuses)
|
||||
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
|
||||
scheduledTootList.setHasFixedSize(true)
|
||||
scheduledTootList.layoutManager = LinearLayoutManager(this)
|
||||
binding.scheduledTootList.setHasFixedSize(true)
|
||||
binding.scheduledTootList.layoutManager = LinearLayoutManager(this)
|
||||
val divider = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
|
||||
scheduledTootList.addItemDecoration(divider)
|
||||
scheduledTootList.adapter = adapter
|
||||
|
||||
viewModel = ViewModelProvider(this, viewModelFactory)[ScheduledTootViewModel::class.java]
|
||||
binding.scheduledTootList.addItemDecoration(divider)
|
||||
binding.scheduledTootList.adapter = adapter
|
||||
|
||||
viewModel.data.observe(this) {
|
||||
adapter.submitList(it)
|
||||
|
@ -72,31 +71,31 @@ class ScheduledTootActivity : BaseActivity(), ScheduledTootActionListener, Injec
|
|||
viewModel.networkState.observe(this) { (status) ->
|
||||
when(status) {
|
||||
Status.SUCCESS -> {
|
||||
progressBar.hide()
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
binding.progressBar.hide()
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
if(viewModel.data.value?.loadedCount == 0) {
|
||||
errorMessageView.setup(R.drawable.elephant_friend_empty, R.string.no_scheduled_status)
|
||||
errorMessageView.show()
|
||||
binding.errorMessageView.setup(R.drawable.elephant_friend_empty, R.string.no_scheduled_status)
|
||||
binding.errorMessageView.show()
|
||||
} else {
|
||||
errorMessageView.hide()
|
||||
binding.errorMessageView.hide()
|
||||
}
|
||||
}
|
||||
Status.RUNNING -> {
|
||||
errorMessageView.hide()
|
||||
binding.errorMessageView.hide()
|
||||
if(viewModel.data.value?.loadedCount ?: 0 > 0) {
|
||||
swipeRefreshLayout.isRefreshing = true
|
||||
binding.swipeRefreshLayout.isRefreshing = true
|
||||
} else {
|
||||
progressBar.show()
|
||||
binding.progressBar.show()
|
||||
}
|
||||
}
|
||||
Status.FAILED -> {
|
||||
if(viewModel.data.value?.loadedCount ?: 0 >= 0) {
|
||||
progressBar.hide()
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
errorMessageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||
binding.progressBar.hide()
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
binding.errorMessageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||
refreshStatuses()
|
||||
}
|
||||
errorMessageView.show()
|
||||
binding.errorMessageView.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,13 +18,11 @@ package com.keylesspalace.tusky.components.scheduled
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.TextView
|
||||
import androidx.paging.PagedListAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.ItemScheduledTootBinding
|
||||
import com.keylesspalace.tusky.entity.ScheduledStatus
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
|
||||
interface ScheduledTootActionListener {
|
||||
fun edit(item: ScheduledStatus)
|
||||
|
@ -33,7 +31,7 @@ interface ScheduledTootActionListener {
|
|||
|
||||
class ScheduledTootAdapter(
|
||||
val listener: ScheduledTootActionListener
|
||||
) : PagedListAdapter<ScheduledStatus, ScheduledTootAdapter.TootViewHolder>(
|
||||
) : PagedListAdapter<ScheduledStatus, BindingHolder<ItemScheduledTootBinding>>(
|
||||
object: DiffUtil.ItemCallback<ScheduledStatus>(){
|
||||
override fun areItemsTheSame(oldItem: ScheduledStatus, newItem: ScheduledStatus): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
|
@ -46,40 +44,24 @@ class ScheduledTootAdapter(
|
|||
}
|
||||
) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TootViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_scheduled_toot, parent, false)
|
||||
return TootViewHolder(view)
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemScheduledTootBinding> {
|
||||
val binding = ItemScheduledTootBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return BindingHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(viewHolder: TootViewHolder, position: Int) {
|
||||
getItem(position)?.let{
|
||||
viewHolder.bind(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inner class TootViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
private val text: TextView = view.findViewById(R.id.text)
|
||||
private val edit: ImageButton = view.findViewById(R.id.edit)
|
||||
private val delete: ImageButton = view.findViewById(R.id.delete)
|
||||
|
||||
fun bind(item: ScheduledStatus) {
|
||||
edit.isEnabled = true
|
||||
delete.isEnabled = true
|
||||
text.text = item.params.text
|
||||
edit.setOnClickListener { v: View ->
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemScheduledTootBinding>, position: Int) {
|
||||
getItem(position)?.let{ item ->
|
||||
holder.binding.edit.isEnabled = true
|
||||
holder.binding.delete.isEnabled = true
|
||||
holder.binding.text.text = item.params.text
|
||||
holder.binding.edit.setOnClickListener { v: View ->
|
||||
v.isEnabled = false
|
||||
listener.edit(item)
|
||||
}
|
||||
delete.setOnClickListener { v: View ->
|
||||
holder.binding.delete.setOnClickListener { v: View ->
|
||||
v.isEnabled = false
|
||||
listener.delete(item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,11 @@ import com.google.android.material.tabs.TabLayoutMediator
|
|||
import com.keylesspalace.tusky.BottomSheetActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.search.adapter.SearchPagerAdapter
|
||||
import com.keylesspalace.tusky.databinding.ActivitySearchBinding
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import kotlinx.android.synthetic.main.activity_search.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class SearchActivity : BottomSheetActivity(), HasAndroidInjector {
|
||||
|
@ -41,10 +42,12 @@ class SearchActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
|
||||
private val viewModel: SearchViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private val binding by viewBinding(ActivitySearchBinding::inflate)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_search)
|
||||
setSupportActionBar(toolbar)
|
||||
setContentView(binding.root)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
supportActionBar?.apply {
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowHomeEnabled(true)
|
||||
|
@ -55,9 +58,9 @@ class SearchActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
}
|
||||
|
||||
private fun setupPages() {
|
||||
pages.adapter = SearchPagerAdapter(this)
|
||||
binding.pages.adapter = SearchPagerAdapter(this)
|
||||
|
||||
TabLayoutMediator(tabs, pages) {
|
||||
TabLayoutMediator(binding.tabs, binding.pages) {
|
||||
tab, position ->
|
||||
tab.text = getPageTitle(position)
|
||||
}.attach()
|
||||
|
|
|
@ -19,24 +19,23 @@ import android.view.LayoutInflater
|
|||
import android.view.ViewGroup
|
||||
import androidx.paging.PagedListAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.adapter.HashtagViewHolder
|
||||
import com.keylesspalace.tusky.databinding.ItemHashtagBinding
|
||||
import com.keylesspalace.tusky.entity.HashTag
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
|
||||
class SearchHashtagsAdapter(private val linkListener: LinkListener)
|
||||
: PagedListAdapter<HashTag, RecyclerView.ViewHolder>(HASHTAG_COMPARATOR) {
|
||||
: PagedListAdapter<HashTag, BindingHolder<ItemHashtagBinding>>(HASHTAG_COMPARATOR) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_hashtag, parent, false)
|
||||
return HashtagViewHolder(view)
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemHashtagBinding> {
|
||||
val binding = ItemHashtagBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return BindingHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemHashtagBinding>, position: Int) {
|
||||
getItem(position)?.let { (name) ->
|
||||
(holder as HashtagViewHolder).setup(name, linkListener)
|
||||
holder.binding.root.text = String.format("#%s", name)
|
||||
holder.binding.root.setOnClickListener { linkListener.onViewTag(name) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,11 +23,10 @@ import com.keylesspalace.tusky.components.search.adapter.SearchAccountsAdapter
|
|||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.NetworkState
|
||||
import kotlinx.android.synthetic.main.fragment_search.*
|
||||
|
||||
class SearchAccountsFragment : SearchFragment<Account>() {
|
||||
override fun createAdapter(): PagedListAdapter<Account, *> {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(searchRecyclerView.context)
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(binding.searchRecyclerView.context)
|
||||
|
||||
return SearchAccountsAdapter(
|
||||
this,
|
||||
|
@ -46,5 +45,4 @@ class SearchAccountsFragment : SearchFragment<Account>() {
|
|||
companion object {
|
||||
fun newInstance() = SearchAccountsFragment()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,11 +17,11 @@ import com.keylesspalace.tusky.BottomSheetActivity
|
|||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.ViewTagActivity
|
||||
import com.keylesspalace.tusky.components.search.SearchViewModel
|
||||
import com.keylesspalace.tusky.databinding.FragmentSearchBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import kotlinx.android.synthetic.main.fragment_search.*
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
||||
|
@ -32,6 +32,8 @@ abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
|||
|
||||
protected val viewModel: SearchViewModel by activityViewModels { viewModelFactory }
|
||||
|
||||
protected val binding by viewBinding(FragmentSearchBinding::bind)
|
||||
|
||||
private var snackbarErrorRetry: Snackbar? = null
|
||||
|
||||
abstract fun createAdapter(): PagedListAdapter<T, *>
|
||||
|
@ -48,8 +50,8 @@ abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
|||
}
|
||||
|
||||
private fun setupSwipeRefreshLayout() {
|
||||
swipeRefreshLayout.setOnRefreshListener(this)
|
||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
binding.swipeRefreshLayout.setOnRefreshListener(this)
|
||||
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
}
|
||||
|
||||
private fun subscribeObservables() {
|
||||
|
@ -59,7 +61,7 @@ abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
|||
|
||||
networkStateRefresh.observe(viewLifecycleOwner) {
|
||||
|
||||
searchProgressBar.visible(it == NetworkState.LOADING)
|
||||
binding.searchProgressBar.visible(it == NetworkState.LOADING)
|
||||
|
||||
if (it.status == Status.FAILED) {
|
||||
showError()
|
||||
|
@ -69,7 +71,7 @@ abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
|||
|
||||
networkState.observe(viewLifecycleOwner) {
|
||||
|
||||
progressBarBottom.visible(it == NetworkState.LOADING)
|
||||
binding.progressBarBottom.visible(it == NetworkState.LOADING)
|
||||
|
||||
if (it.status == Status.FAILED) {
|
||||
showError()
|
||||
|
@ -82,24 +84,25 @@ abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
|||
}
|
||||
|
||||
private fun initAdapter() {
|
||||
searchRecyclerView.addItemDecoration(DividerItemDecoration(searchRecyclerView.context, DividerItemDecoration.VERTICAL))
|
||||
searchRecyclerView.layoutManager = LinearLayoutManager(searchRecyclerView.context)
|
||||
binding.searchRecyclerView.addItemDecoration(DividerItemDecoration(binding.searchRecyclerView.context, DividerItemDecoration.VERTICAL))
|
||||
binding.searchRecyclerView.layoutManager = LinearLayoutManager(binding.searchRecyclerView.context)
|
||||
adapter = createAdapter()
|
||||
searchRecyclerView.adapter = adapter
|
||||
searchRecyclerView.setHasFixedSize(true)
|
||||
(searchRecyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
binding.searchRecyclerView.adapter = adapter
|
||||
binding.searchRecyclerView.setHasFixedSize(true)
|
||||
(binding.searchRecyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
}
|
||||
|
||||
private fun showNoData(isEmpty: Boolean) {
|
||||
if (isEmpty && networkStateRefresh.value == NetworkState.LOADED)
|
||||
searchNoResultsText.show()
|
||||
else
|
||||
searchNoResultsText.hide()
|
||||
if (isEmpty && networkStateRefresh.value == NetworkState.LOADED) {
|
||||
binding.searchNoResultsText.show()
|
||||
} else {
|
||||
binding.searchNoResultsText.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showError() {
|
||||
if (snackbarErrorRetry?.isShown != true) {
|
||||
snackbarErrorRetry = Snackbar.make(layoutRoot, R.string.failed_search, Snackbar.LENGTH_INDEFINITE)
|
||||
snackbarErrorRetry = Snackbar.make(binding.root, R.string.failed_search, Snackbar.LENGTH_INDEFINITE)
|
||||
snackbarErrorRetry?.setAction(R.string.action_retry) {
|
||||
snackbarErrorRetry = null
|
||||
viewModel.retryAllSearches()
|
||||
|
@ -122,8 +125,8 @@ abstract class SearchFragment<T> : Fragment(R.layout.fragment_search),
|
|||
override fun onRefresh() {
|
||||
|
||||
// Dismissed here because the RecyclerView bottomProgressBar is shown as soon as the retry begins.
|
||||
swipeRefreshLayout.post {
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
binding.swipeRefreshLayout.post {
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
}
|
||||
viewModel.retryAllSearches()
|
||||
}
|
||||
|
|
|
@ -63,7 +63,6 @@ import com.keylesspalace.tusky.viewdata.StatusViewData
|
|||
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
||||
import com.uber.autodispose.autoDispose
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.android.synthetic.main.fragment_search.*
|
||||
|
||||
class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concrete>>(), StatusActionListener {
|
||||
|
||||
|
@ -78,7 +77,7 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
get() = super.adapter as SearchStatusesAdapter
|
||||
|
||||
override fun createAdapter(): PagedListAdapter<Pair<Status, StatusViewData.Concrete>, *> {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(searchRecyclerView.context)
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(binding.searchRecyclerView.context)
|
||||
val statusDisplayOptions = StatusDisplayOptions(
|
||||
animateAvatars = preferences.getBoolean("animateGifAvatars", false),
|
||||
mediaPreviewEnabled = viewModel.mediaPreviewEnabled,
|
||||
|
@ -91,12 +90,11 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||
)
|
||||
|
||||
searchRecyclerView.addItemDecoration(DividerItemDecoration(searchRecyclerView.context, DividerItemDecoration.VERTICAL))
|
||||
searchRecyclerView.layoutManager = LinearLayoutManager(searchRecyclerView.context)
|
||||
binding.searchRecyclerView.addItemDecoration(DividerItemDecoration(binding.searchRecyclerView.context, DividerItemDecoration.VERTICAL))
|
||||
binding.searchRecyclerView.layoutManager = LinearLayoutManager(binding.searchRecyclerView.context)
|
||||
return SearchStatusesAdapter(statusDisplayOptions, this)
|
||||
}
|
||||
|
||||
|
||||
override fun onContentHiddenChange(isShowing: Boolean, position: Int) {
|
||||
searchAdapter.getItem(position)?.let {
|
||||
viewModel.contentHiddenChange(it, isShowing)
|
||||
|
@ -486,5 +484,4 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,24 +18,26 @@ package com.keylesspalace.tusky.db
|
|||
import android.text.Spanned
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.core.text.toHtml
|
||||
import androidx.room.ProvidedTypeConverter
|
||||
import androidx.room.TypeConverter
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.keylesspalace.tusky.TabData
|
||||
import com.keylesspalace.tusky.components.conversation.ConversationAccountEntity
|
||||
import com.keylesspalace.tusky.createTabDataFromId
|
||||
import com.keylesspalace.tusky.entity.*
|
||||
import com.keylesspalace.tusky.json.SpannedTypeAdapter
|
||||
import com.keylesspalace.tusky.util.trimTrailingWhitespace
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
class Converters {
|
||||
|
||||
private val gson = GsonBuilder()
|
||||
.registerTypeAdapter(Spanned::class.java, SpannedTypeAdapter())
|
||||
.create()
|
||||
@ProvidedTypeConverter
|
||||
@Singleton
|
||||
class Converters @Inject constructor (
|
||||
private val gson: Gson
|
||||
) {
|
||||
|
||||
@TypeConverter
|
||||
fun jsonToEmojiList(emojiListJson: String?): List<Emoji>? {
|
||||
|
|
|
@ -23,7 +23,7 @@ import androidx.room.PrimaryKey
|
|||
import androidx.room.TypeConverters
|
||||
import com.keylesspalace.tusky.entity.NewPoll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Entity
|
||||
@TypeConverters(Converters::class)
|
||||
|
|
|
@ -28,6 +28,7 @@ import com.keylesspalace.tusky.appstore.EventHubImpl
|
|||
import com.keylesspalace.tusky.components.notifications.Notifier
|
||||
import com.keylesspalace.tusky.components.notifications.SystemNotifier
|
||||
import com.keylesspalace.tusky.db.AppDatabase
|
||||
import com.keylesspalace.tusky.db.Converters
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.network.TimelineCases
|
||||
import com.keylesspalace.tusky.network.TimelineCasesImpl
|
||||
|
@ -70,8 +71,9 @@ class AppModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesDatabase(appContext: Context): AppDatabase {
|
||||
fun providesDatabase(appContext: Context, converters: Converters): AppDatabase {
|
||||
return Room.databaseBuilder(appContext, AppDatabase::class.java, "tuskyDB")
|
||||
.addTypeConverter(converters)
|
||||
.allowMainThreadQueries()
|
||||
.addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5,
|
||||
AppDatabase.MIGRATION_5_6, AppDatabase.MIGRATION_6_7, AppDatabase.MIGRATION_7_8,
|
||||
|
|
|
@ -22,7 +22,7 @@ import com.google.gson.JsonElement
|
|||
import com.google.gson.JsonParseException
|
||||
import com.google.gson.annotations.JsonAdapter
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class Attachment(
|
||||
|
|
|
@ -17,7 +17,7 @@ package com.keylesspalace.tusky.entity
|
|||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class Emoji(
|
||||
|
|
|
@ -17,7 +17,7 @@ package com.keylesspalace.tusky.entity
|
|||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
data class NewStatus(
|
||||
val status: String,
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.view.View
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -31,6 +32,8 @@ import com.keylesspalace.tusky.AccountListActivity.Type
|
|||
import com.keylesspalace.tusky.BaseActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.adapter.*
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.databinding.FragmentAccountListBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.entity.Relationship
|
||||
|
@ -40,12 +43,12 @@ import com.keylesspalace.tusky.settings.PrefKeys
|
|||
import com.keylesspalace.tusky.util.HttpHeaderLink
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener
|
||||
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
||||
import com.uber.autodispose.autoDispose
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.android.synthetic.main.fragment_account_list.*
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
@ -55,6 +58,10 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
|||
|
||||
@Inject
|
||||
lateinit var api: MastodonApi
|
||||
@Inject
|
||||
lateinit var accountManager: AccountManager
|
||||
|
||||
private val binding by viewBinding(FragmentAccountListBinding::bind)
|
||||
|
||||
private lateinit var type: Type
|
||||
private var id: String? = null
|
||||
|
@ -73,12 +80,12 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
val layoutManager = LinearLayoutManager(view.context)
|
||||
recyclerView.layoutManager = layoutManager
|
||||
(recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
binding.recyclerView.layoutManager = layoutManager
|
||||
(binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
|
||||
recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
||||
binding.recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
||||
|
||||
val pm = PreferenceManager.getDefaultSharedPreferences(view.context)
|
||||
val animateAvatar = pm.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
|
||||
|
@ -87,10 +94,17 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
|||
adapter = when (type) {
|
||||
Type.BLOCKS -> BlocksAdapter(this, animateAvatar, animateEmojis)
|
||||
Type.MUTES -> MutesAdapter(this, animateAvatar, animateEmojis)
|
||||
Type.FOLLOW_REQUESTS -> FollowRequestsAdapter(this, animateAvatar, animateEmojis)
|
||||
Type.FOLLOW_REQUESTS -> {
|
||||
val headerAdapter = FollowRequestsHeaderAdapter(accountManager.activeAccount!!.domain, arguments?.get(ARG_ACCOUNT_LOCKED) == true)
|
||||
val followRequestsAdapter = FollowRequestsAdapter(this, animateAvatar, animateEmojis)
|
||||
binding.recyclerView.adapter = ConcatAdapter(headerAdapter, followRequestsAdapter)
|
||||
followRequestsAdapter
|
||||
}
|
||||
else -> FollowAdapter(this, animateAvatar, animateEmojis)
|
||||
}
|
||||
recyclerView.adapter = adapter
|
||||
if (binding.recyclerView.adapter == null) {
|
||||
binding.recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
scrollListener = object : EndlessOnScrollListener(layoutManager) {
|
||||
override fun onLoadMore(totalItemsCount: Int, view: RecyclerView) {
|
||||
|
@ -101,7 +115,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
|||
}
|
||||
}
|
||||
|
||||
recyclerView.addOnScrollListener(scrollListener)
|
||||
binding.recyclerView.addOnScrollListener(scrollListener)
|
||||
|
||||
fetchAccounts()
|
||||
}
|
||||
|
@ -136,7 +150,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
|||
val unmutedUser = mutesAdapter.removeItem(position)
|
||||
|
||||
if (unmutedUser != null) {
|
||||
Snackbar.make(recyclerView, R.string.confirmation_unmuted, Snackbar.LENGTH_LONG)
|
||||
Snackbar.make(binding.recyclerView, R.string.confirmation_unmuted, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_undo) {
|
||||
mutesAdapter.addItem(unmutedUser, position)
|
||||
onMute(true, id, position, notifications)
|
||||
|
@ -180,7 +194,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
|||
val unblockedUser = blocksAdapter.removeItem(position)
|
||||
|
||||
if (unblockedUser != null) {
|
||||
Snackbar.make(recyclerView, R.string.confirmation_unblocked, Snackbar.LENGTH_LONG)
|
||||
Snackbar.make(binding.recyclerView, R.string.confirmation_unblocked, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_undo) {
|
||||
blocksAdapter.addItem(unblockedUser, position)
|
||||
onBlock(true, id, position)
|
||||
|
@ -260,7 +274,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
|||
fetching = true
|
||||
|
||||
if (fromId != null) {
|
||||
recyclerView.post { adapter.setBottomLoading(true) }
|
||||
binding.recyclerView.post { adapter.setBottomLoading(true) }
|
||||
}
|
||||
|
||||
getFetchCallByListType(fromId)
|
||||
|
@ -303,14 +317,14 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
|||
fetching = false
|
||||
|
||||
if (adapter.itemCount == 0) {
|
||||
messageView.show()
|
||||
messageView.setup(
|
||||
binding.messageView.show()
|
||||
binding.messageView.setup(
|
||||
R.drawable.elephant_friend_empty,
|
||||
R.string.message_empty,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
messageView.hide()
|
||||
binding.messageView.hide()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,15 +353,15 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
|||
Log.e(TAG, "Fetch failure", throwable)
|
||||
|
||||
if (adapter.itemCount == 0) {
|
||||
messageView.show()
|
||||
binding.messageView.show()
|
||||
if (throwable is IOException) {
|
||||
messageView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||
messageView.hide()
|
||||
binding.messageView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||
binding.messageView.hide()
|
||||
this.fetchAccounts(null)
|
||||
}
|
||||
} else {
|
||||
messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||
messageView.hide()
|
||||
binding.messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||
binding.messageView.hide()
|
||||
this.fetchAccounts(null)
|
||||
}
|
||||
}
|
||||
|
@ -358,15 +372,16 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
|||
private const val TAG = "AccountList" // logging tag
|
||||
private const val ARG_TYPE = "type"
|
||||
private const val ARG_ID = "id"
|
||||
private const val ARG_ACCOUNT_LOCKED = "acc_locked"
|
||||
|
||||
fun newInstance(type: Type, id: String? = null): AccountListFragment {
|
||||
fun newInstance(type: Type, id: String? = null, accountLocked: Boolean = false): AccountListFragment {
|
||||
return AccountListFragment().apply {
|
||||
arguments = Bundle(2).apply {
|
||||
putSerializable(ARG_TYPE, type)
|
||||
putString(ARG_ID, id)
|
||||
putBoolean(ARG_ACCOUNT_LOCKED, accountLocked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.bumptech.glide.Glide
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.ViewMediaActivity
|
||||
import com.keylesspalace.tusky.databinding.FragmentTimelineBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
|
@ -39,13 +40,13 @@ import com.keylesspalace.tusky.util.LinkHelper
|
|||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.view.SquareImageView
|
||||
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
||||
import com.uber.autodispose.android.lifecycle.autoDispose
|
||||
import io.reactivex.SingleObserver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import kotlinx.android.synthetic.main.fragment_timeline.*
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
@ -58,49 +59,36 @@ import javax.inject.Inject
|
|||
*/
|
||||
|
||||
class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFragment, Injectable {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(accountId: String, enableSwipeToRefresh:Boolean=true): AccountMediaFragment {
|
||||
val fragment = AccountMediaFragment()
|
||||
val args = Bundle()
|
||||
args.putString(ACCOUNT_ID_ARG, accountId)
|
||||
args.putBoolean(ARG_ENABLE_SWIPE_TO_REFRESH,enableSwipeToRefresh)
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
|
||||
private const val ACCOUNT_ID_ARG = "account_id"
|
||||
private const val TAG = "AccountMediaFragment"
|
||||
private const val ARG_ENABLE_SWIPE_TO_REFRESH = "arg.enable.swipe.to.refresh"
|
||||
}
|
||||
|
||||
private var isSwipeToRefreshEnabled: Boolean = true
|
||||
private var needToRefresh = false
|
||||
|
||||
@Inject
|
||||
lateinit var api: MastodonApi
|
||||
|
||||
private val binding by viewBinding(FragmentTimelineBinding::bind)
|
||||
|
||||
private lateinit var accountId: String
|
||||
|
||||
private val adapter = MediaGridAdapter()
|
||||
private val statuses = mutableListOf<Status>()
|
||||
private var fetchingStatus = FetchingStatus.NOT_FETCHING
|
||||
|
||||
private lateinit var accountId: String
|
||||
private var isSwipeToRefreshEnabled: Boolean = true
|
||||
private var needToRefresh = false
|
||||
|
||||
private val callback = object : SingleObserver<Response<List<Status>>> {
|
||||
override fun onError(t: Throwable) {
|
||||
fetchingStatus = FetchingStatus.NOT_FETCHING
|
||||
|
||||
if (isAdded) {
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
progressBar.visibility = View.GONE
|
||||
topProgressBar?.hide()
|
||||
statusView.show()
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.topProgressBar.hide()
|
||||
binding.statusView.show()
|
||||
if (t is IOException) {
|
||||
statusView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||
binding.statusView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||
doInitialLoadingIfNeeded()
|
||||
}
|
||||
} else {
|
||||
statusView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||
binding.statusView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||
doInitialLoadingIfNeeded()
|
||||
}
|
||||
}
|
||||
|
@ -112,9 +100,9 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr
|
|||
override fun onSuccess(response: Response<List<Status>>) {
|
||||
fetchingStatus = FetchingStatus.NOT_FETCHING
|
||||
if (isAdded) {
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
progressBar.visibility = View.GONE
|
||||
topProgressBar?.hide()
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.topProgressBar.hide()
|
||||
|
||||
val body = response.body()
|
||||
body?.let { fetched ->
|
||||
|
@ -126,11 +114,11 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr
|
|||
}
|
||||
adapter.addTop(result)
|
||||
if (result.isNotEmpty())
|
||||
recyclerView.scrollToPosition(0)
|
||||
binding.recyclerView.scrollToPosition(0)
|
||||
|
||||
if (statuses.isEmpty()) {
|
||||
statusView.show()
|
||||
statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty)
|
||||
binding.statusView.show()
|
||||
binding.statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -181,18 +169,18 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr
|
|||
|
||||
adapter.baseItemColor = ThemeUtils.getColor(view.context, android.R.attr.windowBackground)
|
||||
|
||||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.adapter = adapter
|
||||
binding.recyclerView.layoutManager = layoutManager
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
if (isSwipeToRefreshEnabled) {
|
||||
swipeRefreshLayout.setOnRefreshListener {
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
refresh()
|
||||
}
|
||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
}
|
||||
statusView.visibility = View.GONE
|
||||
binding.statusView.visibility = View.GONE
|
||||
|
||||
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
|
||||
override fun onScrolled(recycler_view: RecyclerView, dx: Int, dy: Int) {
|
||||
if (dy > 0) {
|
||||
|
@ -216,7 +204,7 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr
|
|||
}
|
||||
|
||||
private fun refresh() {
|
||||
statusView.hide()
|
||||
binding.statusView.hide()
|
||||
if (fetchingStatus != FetchingStatus.NOT_FETCHING) return
|
||||
if (statuses.isEmpty()) {
|
||||
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
||||
|
@ -229,12 +217,12 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr
|
|||
.subscribe(callback)
|
||||
|
||||
if (!isSwipeToRefreshEnabled)
|
||||
topProgressBar?.show()
|
||||
binding.topProgressBar.show()
|
||||
}
|
||||
|
||||
private fun doInitialLoadingIfNeeded() {
|
||||
if (isAdded) {
|
||||
statusView.hide()
|
||||
binding.statusView.hide()
|
||||
}
|
||||
if (fetchingStatus == FetchingStatus.NOT_FETCHING && statuses.isEmpty()) {
|
||||
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
||||
|
@ -332,7 +320,7 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr
|
|||
|
||||
// saving some allocations
|
||||
override fun onClick(v: View?) {
|
||||
viewMedia(items, adapterPosition, imageView)
|
||||
viewMedia(items, bindingAdapterPosition, imageView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -344,4 +332,19 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr
|
|||
needToRefresh = true
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(accountId: String, enableSwipeToRefresh:Boolean=true): AccountMediaFragment {
|
||||
val fragment = AccountMediaFragment()
|
||||
val args = Bundle()
|
||||
args.putString(ACCOUNT_ID_ARG, accountId)
|
||||
args.putBoolean(ARG_ENABLE_SWIPE_TO_REFRESH,enableSwipeToRefresh)
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
|
||||
private const val ACCOUNT_ID_ARG = "account_id"
|
||||
private const val TAG = "AccountMediaFragment"
|
||||
private const val ARG_ENABLE_SWIPE_TO_REFRESH = "arg.enable.swipe.to.refresh"
|
||||
}
|
||||
}
|
|
@ -863,6 +863,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
adapter.setMediaPreviewEnabled(enabled);
|
||||
fullyRefresh();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "showNotificationsFilter": {
|
||||
if (isAdded()) {
|
||||
|
@ -870,6 +871,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
updateFilterVisibility();
|
||||
fullyRefreshWithProgressBar(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -511,7 +511,7 @@ public abstract class SFragment extends Fragment implements Injectable {
|
|||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
|
||||
public void reloadFilters(boolean forceRefresh) {
|
||||
if (filters != null && !forceRefresh) {
|
||||
applyFilters(forceRefresh);
|
||||
|
@ -547,7 +547,7 @@ public abstract class SFragment extends Fragment implements Injectable {
|
|||
// Override to refresh your fragment
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
|
||||
public boolean shouldFilterStatus(Status status) {
|
||||
|
||||
if (filterRemoveRegex && status.getPoll() != null) {
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
/* Copyright 2019 kyori19
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.fragment;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.app.TimePickerDialog;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.keylesspalace.tusky.components.compose.ComposeActivity;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class TimePickerFragment extends DialogFragment {
|
||||
|
||||
public static final String PICKER_TIME_HOUR = "picker_time_hour";
|
||||
public static final String PICKER_TIME_MINUTE = "picker_time_minute";
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Bundle args = getArguments();
|
||||
Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
|
||||
if (args != null) {
|
||||
calendar.set(Calendar.HOUR_OF_DAY, args.getInt(PICKER_TIME_HOUR));
|
||||
calendar.set(Calendar.MINUTE, args.getInt(PICKER_TIME_MINUTE));
|
||||
}
|
||||
|
||||
return new TimePickerDialog(getContext(),
|
||||
android.R.style.Theme_DeviceDefault_Dialog,
|
||||
(ComposeActivity) getActivity(),
|
||||
calendar.get(Calendar.HOUR_OF_DAY),
|
||||
calendar.get(Calendar.MINUTE),
|
||||
true);
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -32,13 +32,12 @@ import com.bumptech.glide.load.engine.GlideException
|
|||
import com.bumptech.glide.request.RequestListener
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.github.chrisbanes.photoview.PhotoViewAttacher
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.ViewMediaActivity
|
||||
import com.keylesspalace.tusky.databinding.FragmentViewImageBinding
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
import io.reactivex.subjects.BehaviorSubject
|
||||
import kotlinx.android.synthetic.main.activity_view_media.*
|
||||
import kotlinx.android.synthetic.main.fragment_view_image.*
|
||||
import kotlin.math.abs
|
||||
|
||||
class ViewImageFragment : ViewMediaFragment() {
|
||||
|
@ -48,6 +47,9 @@ class ViewImageFragment : ViewMediaFragment() {
|
|||
fun onPhotoTap()
|
||||
}
|
||||
|
||||
private var _binding: FragmentViewImageBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private lateinit var attacher: PhotoViewAttacher
|
||||
private lateinit var photoActionsListener: PhotoActionsListener
|
||||
private lateinit var toolbar: View
|
||||
|
@ -71,18 +73,19 @@ class ViewImageFragment : ViewMediaFragment() {
|
|||
description: String?,
|
||||
showingDescription: Boolean
|
||||
) {
|
||||
photoView.transitionName = url
|
||||
mediaDescription.text = description
|
||||
captionSheet.visible(showingDescription)
|
||||
binding.photoView.transitionName = url
|
||||
binding.mediaDescription.text = description
|
||||
binding.captionSheet.visible(showingDescription)
|
||||
|
||||
startedTransition = false
|
||||
loadImageFromNetwork(url, previewUrl, photoView)
|
||||
loadImageFromNetwork(url, previewUrl, binding.photoView)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
toolbar = requireActivity().toolbar
|
||||
toolbar = (requireActivity() as ViewMediaActivity).toolbar
|
||||
this.transition = BehaviorSubject.create()
|
||||
return inflater.inflate(R.layout.fragment_view_image, container, false)
|
||||
_binding = FragmentViewImageBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
|
@ -105,7 +108,7 @@ class ViewImageFragment : ViewMediaFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
attacher = PhotoViewAttacher(photoView).apply {
|
||||
attacher = PhotoViewAttacher(binding.photoView).apply {
|
||||
// This prevents conflicts with ViewPager
|
||||
setAllowParentInterceptOnEdge(true)
|
||||
|
||||
|
@ -127,7 +130,7 @@ class ViewImageFragment : ViewMediaFragment() {
|
|||
|
||||
var lastY = 0f
|
||||
|
||||
photoView.setOnTouchListener { v, event ->
|
||||
binding.photoView.setOnTouchListener { v, event ->
|
||||
// This part is for scaling/translating on vertical move.
|
||||
// We use raw coordinates to get the correct ones during scaling
|
||||
|
||||
|
@ -140,11 +143,11 @@ class ViewImageFragment : ViewMediaFragment() {
|
|||
val diff = event.rawY - lastY
|
||||
// This code is to prevent transformations during page scrolling
|
||||
// If we are already translating or we reached the threshold, then transform.
|
||||
if (photoView.translationY != 0f || abs(diff) > 40) {
|
||||
photoView.translationY += (diff)
|
||||
val scale = (-abs(photoView.translationY) / 720 + 1).coerceAtLeast(0.5f)
|
||||
photoView.scaleY = scale
|
||||
photoView.scaleX = scale
|
||||
if (binding.photoView.translationY != 0f || abs(diff) > 40) {
|
||||
binding.photoView.translationY += (diff)
|
||||
val scale = (-abs(binding.photoView.translationY) / 720 + 1).coerceAtLeast(0.5f)
|
||||
binding.photoView.scaleY = scale
|
||||
binding.photoView.scaleX = scale
|
||||
lastY = event.rawY
|
||||
return@setOnTouchListener true
|
||||
}
|
||||
|
@ -158,13 +161,13 @@ class ViewImageFragment : ViewMediaFragment() {
|
|||
}
|
||||
|
||||
private fun onGestureEnd() {
|
||||
if (photoView == null) {
|
||||
if (_binding == null) {
|
||||
return
|
||||
}
|
||||
if (abs(photoView.translationY) > 180) {
|
||||
if (abs(binding.photoView.translationY) > 180) {
|
||||
photoActionsListener.onDismiss()
|
||||
} else {
|
||||
photoView.animate().translationY(0f).scaleX(1f).scaleY(1f).start()
|
||||
binding.photoView.animate().translationY(0f).scaleX(1f).scaleY(1f).start()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,15 +176,17 @@ class ViewImageFragment : ViewMediaFragment() {
|
|||
}
|
||||
|
||||
override fun onToolbarVisibilityChange(visible: Boolean) {
|
||||
if (photoView == null || !userVisibleHint || captionSheet == null) {
|
||||
if (_binding == null || !userVisibleHint ) {
|
||||
return
|
||||
}
|
||||
isDescriptionVisible = showingDescription && visible
|
||||
val alpha = if (isDescriptionVisible) 1.0f else 0.0f
|
||||
captionSheet.animate().alpha(alpha)
|
||||
binding.captionSheet.animate().alpha(alpha)
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
captionSheet?.visible(isDescriptionVisible)
|
||||
if (_binding != null) {
|
||||
binding.captionSheet.visible(isDescriptionVisible)
|
||||
}
|
||||
animation.removeListener(this)
|
||||
}
|
||||
})
|
||||
|
@ -189,8 +194,9 @@ class ViewImageFragment : ViewMediaFragment() {
|
|||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
Glide.with(this).clear(photoView)
|
||||
Glide.with(this).clear(binding.photoView)
|
||||
transition.onComplete()
|
||||
_binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
|
@ -253,7 +259,7 @@ class ViewImageFragment : ViewMediaFragment() {
|
|||
photoActionsListener.onBringUp()
|
||||
}
|
||||
// Hide progress bar only on fail request from internet
|
||||
if (!isCacheRequest) progressBar?.hide()
|
||||
if (!isCacheRequest && _binding != null) binding.progressBar.hide()
|
||||
// We don't want to overwrite preview with null when main image fails to load
|
||||
return !isCacheRequest
|
||||
}
|
||||
|
@ -261,14 +267,16 @@ class ViewImageFragment : ViewMediaFragment() {
|
|||
@SuppressLint("CheckResult")
|
||||
override fun onResourceReady(resource: Drawable, model: Any, target: Target<Drawable>,
|
||||
dataSource: DataSource, isFirstResource: Boolean): Boolean {
|
||||
progressBar?.hide() // Always hide the progress bar on success
|
||||
if (_binding != null) {
|
||||
binding.progressBar.hide() // Always hide the progress bar on success
|
||||
}
|
||||
|
||||
if (!startedTransition || !shouldStartTransition) {
|
||||
// Set this right away so that we don't have to concurrent post() requests
|
||||
startedTransition = true
|
||||
// post() because load() replaces image with null. Sometimes after we set
|
||||
// the thumbnail.
|
||||
photoView.post {
|
||||
binding.photoView.post {
|
||||
target.onResourceReady(resource, null)
|
||||
if (shouldStartTransition) photoActionsListener.onBringUp()
|
||||
}
|
||||
|
|
|
@ -26,16 +26,18 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.MediaController
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.ViewMediaActivity
|
||||
import com.keylesspalace.tusky.databinding.FragmentViewVideoBinding
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
import com.keylesspalace.tusky.view.ExposedPlayPauseVideoView
|
||||
import kotlinx.android.synthetic.main.activity_view_media.*
|
||||
import kotlinx.android.synthetic.main.fragment_view_video.*
|
||||
|
||||
class ViewVideoFragment : ViewMediaFragment() {
|
||||
|
||||
private var _binding: FragmentViewVideoBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private lateinit var toolbar: View
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private val hideToolbar = Runnable {
|
||||
|
@ -52,7 +54,7 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
|
||||
// Start/pause/resume video playback as fragment is shown/hidden
|
||||
super.setUserVisibleHint(isVisibleToUser)
|
||||
if (videoView == null) {
|
||||
if (_binding == null) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -60,10 +62,10 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
if (mediaActivity.isToolbarVisible) {
|
||||
handler.postDelayed(hideToolbar, TOOLBAR_HIDE_DELAY_MS)
|
||||
}
|
||||
videoView.start()
|
||||
binding.videoView.start()
|
||||
} else {
|
||||
handler.removeCallbacks(hideToolbar)
|
||||
videoView.pause()
|
||||
binding.videoView.pause()
|
||||
mediaController.hide()
|
||||
}
|
||||
}
|
||||
|
@ -75,11 +77,11 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
description: String?,
|
||||
showingDescription: Boolean
|
||||
) {
|
||||
mediaDescription.text = description
|
||||
mediaDescription.visible(showingDescription)
|
||||
binding.mediaDescription.text = description
|
||||
binding.mediaDescription.visible(showingDescription)
|
||||
|
||||
videoView.transitionName = url
|
||||
videoView.setVideoPath(url)
|
||||
binding.videoView.transitionName = url
|
||||
binding.videoView.setVideoPath(url)
|
||||
mediaController = object : MediaController(mediaActivity) {
|
||||
override fun show(timeout: Int) {
|
||||
// We're doing manual auto-close management.
|
||||
|
@ -100,10 +102,10 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
mediaController.setMediaPlayer(videoView)
|
||||
videoView.setMediaController(mediaController)
|
||||
videoView.requestFocus()
|
||||
videoView.setPlayPauseListener(object: ExposedPlayPauseVideoView.PlayPauseListener {
|
||||
mediaController.setMediaPlayer(binding.videoView)
|
||||
binding.videoView.setMediaController(mediaController)
|
||||
binding.videoView.requestFocus()
|
||||
binding.videoView.setPlayPauseListener(object: ExposedPlayPauseVideoView.PlayPauseListener {
|
||||
override fun onPause() {
|
||||
handler.removeCallbacks(hideToolbar)
|
||||
}
|
||||
|
@ -117,31 +119,31 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
}
|
||||
}
|
||||
})
|
||||
videoView.setOnPreparedListener { mp ->
|
||||
val containerWidth = videoContainer.measuredWidth.toFloat()
|
||||
val containerHeight = videoContainer.measuredHeight.toFloat()
|
||||
binding.videoView.setOnPreparedListener { mp ->
|
||||
val containerWidth = binding.videoContainer.measuredWidth.toFloat()
|
||||
val containerHeight = binding.videoContainer.measuredHeight.toFloat()
|
||||
val videoWidth = mp.videoWidth.toFloat()
|
||||
val videoHeight = mp.videoHeight.toFloat()
|
||||
|
||||
if(containerWidth/containerHeight > videoWidth/videoHeight) {
|
||||
videoView.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
videoView.layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
binding.videoView.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.videoView.layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
} else {
|
||||
videoView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
videoView.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.videoView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
binding.videoView.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
|
||||
// Wait until the media is loaded before accepting taps as we don't want toolbar to
|
||||
// be hidden until then.
|
||||
videoView.setOnTouchListener { _, _ ->
|
||||
binding.videoView.setOnTouchListener { _, _ ->
|
||||
mediaActivity.onPhotoTap()
|
||||
false
|
||||
}
|
||||
|
||||
progressBar.hide()
|
||||
binding.progressBar.hide()
|
||||
mp.isLooping = true
|
||||
if (requireArguments().getBoolean(ARG_START_POSTPONED_TRANSITION)) {
|
||||
videoView.start()
|
||||
binding.videoView.start()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,9 +157,10 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
toolbar = requireActivity().toolbar
|
||||
mediaActivity = activity as ViewMediaActivity
|
||||
return inflater.inflate(R.layout.fragment_view_video, container, false)
|
||||
toolbar = mediaActivity.toolbar
|
||||
_binding = FragmentViewVideoBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -174,7 +177,7 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
}
|
||||
|
||||
override fun onToolbarVisibilityChange(visible: Boolean) {
|
||||
if (videoView == null || mediaDescription == null || !userVisibleHint) {
|
||||
if (_binding == null || !userVisibleHint) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -182,20 +185,22 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
val alpha = if (isDescriptionVisible) 1.0f else 0.0f
|
||||
if (isDescriptionVisible) {
|
||||
// If to be visible, need to make visible immediately and animate alpha
|
||||
mediaDescription.alpha = 0.0f
|
||||
mediaDescription.visible(isDescriptionVisible)
|
||||
binding.mediaDescription.alpha = 0.0f
|
||||
binding.mediaDescription.visible(isDescriptionVisible)
|
||||
}
|
||||
|
||||
mediaDescription.animate().alpha(alpha)
|
||||
binding.mediaDescription.animate().alpha(alpha)
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
mediaDescription?.visible(isDescriptionVisible)
|
||||
if (_binding != null) {
|
||||
binding.mediaDescription.visible(isDescriptionVisible)
|
||||
}
|
||||
animation.removeListener(this)
|
||||
}
|
||||
})
|
||||
.start()
|
||||
|
||||
if (visible && videoView.isPlaying && !isAudio) {
|
||||
if (visible && binding.videoView.isPlaying && !isAudio) {
|
||||
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
|
||||
} else {
|
||||
handler.removeCallbacks(hideToolbar)
|
||||
|
@ -204,4 +209,9 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
|
||||
override fun onTransitionEnd() {
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -298,7 +298,7 @@ fun Account.toEntity(accountId: Long, gson: Gson): TimelineAccountEntity {
|
|||
timelineUserId = accountId,
|
||||
localUsername = localUsername,
|
||||
username = username,
|
||||
displayName = displayName.orEmpty(),
|
||||
displayName = name,
|
||||
url = url,
|
||||
avatar = avatar,
|
||||
emojis = gson.toJson(emojis),
|
||||
|
|
|
@ -28,7 +28,7 @@ import com.keylesspalace.tusky.entity.Status
|
|||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.SaveTootHelper
|
||||
import dagger.android.AndroidInjection
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
|
|
@ -3,6 +3,6 @@ package com.keylesspalace.tusky.util
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
|
||||
class BindingViewHolder<T : ViewBinding>(
|
||||
class BindingHolder<T : ViewBinding>(
|
||||
val binding: T
|
||||
) : RecyclerView.ViewHolder(binding.root)
|
|
@ -256,27 +256,27 @@ class EmojiCompatFont(
|
|||
private const val CHUNK_SIZE = 4096L
|
||||
|
||||
// The system font gets some special behavior...
|
||||
private val SYSTEM_DEFAULT = EmojiCompatFont("system-default",
|
||||
val SYSTEM_DEFAULT = EmojiCompatFont("system-default",
|
||||
"System Default",
|
||||
R.string.caption_systememoji,
|
||||
R.drawable.ic_emoji_34dp,
|
||||
"",
|
||||
"0")
|
||||
private val BLOBMOJI = EmojiCompatFont("Blobmoji",
|
||||
val BLOBMOJI = EmojiCompatFont("Blobmoji",
|
||||
"Blobmoji",
|
||||
R.string.caption_blobmoji,
|
||||
R.drawable.ic_blobmoji,
|
||||
"https://tusky.app/hosted/emoji/BlobmojiCompat.ttf",
|
||||
"12.0.0"
|
||||
)
|
||||
private val TWEMOJI = EmojiCompatFont("Twemoji",
|
||||
val TWEMOJI = EmojiCompatFont("Twemoji",
|
||||
"Twemoji",
|
||||
R.string.caption_twemoji,
|
||||
R.drawable.ic_twemoji,
|
||||
"https://tusky.app/hosted/emoji/TwemojiCompat.ttf",
|
||||
"12.0.0"
|
||||
)
|
||||
private val NOTOEMOJI = EmojiCompatFont("NotoEmoji",
|
||||
val NOTOEMOJI = EmojiCompatFont("NotoEmoji",
|
||||
"Noto Emoji",
|
||||
R.string.caption_notoemoji,
|
||||
R.drawable.ic_notoemoji,
|
||||
|
|
|
@ -22,7 +22,7 @@ import com.keylesspalace.tusky.viewdata.StatusViewData
|
|||
import kotlin.math.min
|
||||
|
||||
// Not using lambdas because there's boxing of int then
|
||||
interface StatusProvider {
|
||||
fun interface StatusProvider {
|
||||
fun getStatus(pos: Int): StatusViewData?
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
package com.keylesspalace.tusky.util
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c
|
||||
*/
|
||||
|
||||
inline fun <T : ViewBinding> AppCompatActivity.viewBinding(
|
||||
crossinline bindingInflater: (LayoutInflater) -> T
|
||||
) = lazy(LazyThreadSafetyMode.NONE) {
|
||||
bindingInflater(layoutInflater)
|
||||
}
|
||||
|
||||
class FragmentViewBindingDelegate<T : ViewBinding>(
|
||||
val fragment: Fragment,
|
||||
val viewBindingFactory: (View) -> T
|
||||
) : ReadOnlyProperty<Fragment, T> {
|
||||
private var binding: T? = null
|
||||
|
||||
init {
|
||||
fragment.lifecycle.addObserver(
|
||||
object : DefaultLifecycleObserver {
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
fragment.viewLifecycleOwnerLiveData.observe(
|
||||
fragment,
|
||||
{ t ->
|
||||
t?.lifecycle?.addObserver(
|
||||
object : DefaultLifecycleObserver {
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
binding = null
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
|
||||
val binding = binding
|
||||
if (binding != null) {
|
||||
return binding
|
||||
}
|
||||
|
||||
val lifecycle = fragment.viewLifecycleOwner.lifecycle
|
||||
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
|
||||
throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
|
||||
}
|
||||
|
||||
return viewBindingFactory(thisRef.requireView()).also { this@FragmentViewBindingDelegate.binding = it }
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) =
|
||||
FragmentViewBindingDelegate(this, viewBindingFactory)
|
|
@ -52,7 +52,7 @@ public final class ViewDataUtils {
|
|||
.setSensitive(visibleStatus.getSensitive())
|
||||
.setIsShowingSensitiveContent(alwaysShowSensitiveMedia || !visibleStatus.getSensitive())
|
||||
.setSpoilerText(visibleStatus.getSpoilerText())
|
||||
.setRebloggedByUsername(status.getReblog() == null ? null : status.getAccount().getDisplayName())
|
||||
.setRebloggedByUsername(status.getReblog() == null ? null : status.getAccount().getName())
|
||||
.setUserFullName(visibleStatus.getAccount().getName())
|
||||
.setVisibility(visibleStatus.getVisibility())
|
||||
.setSenderId(visibleStatus.getAccount().getId())
|
||||
|
|
|
@ -3,14 +3,14 @@ package com.keylesspalace.tusky.view
|
|||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.ViewBackgroundMessageBinding
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
import kotlinx.android.synthetic.main.view_background_message.view.*
|
||||
|
||||
|
||||
/**
|
||||
* This view is used for screens with downloadable content which may fail.
|
||||
|
@ -22,8 +22,9 @@ class BackgroundMessageView @JvmOverloads constructor(
|
|||
defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val binding = ViewBackgroundMessageBinding.inflate(LayoutInflater.from(context), this)
|
||||
|
||||
init {
|
||||
View.inflate(context, R.layout.view_background_message, this)
|
||||
gravity = Gravity.CENTER_HORIZONTAL
|
||||
orientation = VERTICAL
|
||||
|
||||
|
@ -36,11 +37,14 @@ class BackgroundMessageView @JvmOverloads constructor(
|
|||
* Setup image, message and button.
|
||||
* If [clickListener] is `null` then the button will be hidden.
|
||||
*/
|
||||
fun setup(@DrawableRes imageRes: Int, @StringRes messageRes: Int,
|
||||
clickListener: ((v: View) -> Unit)? = null) {
|
||||
messageTextView.setText(messageRes)
|
||||
imageView.setImageResource(imageRes)
|
||||
button.setOnClickListener(clickListener)
|
||||
button.visible(clickListener != null)
|
||||
fun setup(
|
||||
@DrawableRes imageRes: Int,
|
||||
@StringRes messageRes: Int,
|
||||
clickListener: ((v: View) -> Unit)? = null
|
||||
) {
|
||||
binding.messageTextView.setText(messageRes)
|
||||
binding.imageView.setImageResource(imageRes)
|
||||
binding.button.setOnClickListener(clickListener)
|
||||
binding.button.visible(clickListener != null)
|
||||
}
|
||||
}
|
|
@ -17,12 +17,13 @@ package com.keylesspalace.tusky.view
|
|||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.CardLicenseBinding
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import kotlinx.android.synthetic.main.card_license.view.*
|
||||
|
||||
class LicenseCard
|
||||
@JvmOverloads constructor(
|
||||
|
@ -32,7 +33,7 @@ class LicenseCard
|
|||
) : MaterialCardView(context, attrs, defStyleAttr) {
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.card_license, this)
|
||||
val binding = CardLicenseBinding.inflate(LayoutInflater.from(context), this)
|
||||
|
||||
setCardBackgroundColor(ThemeUtils.getColor(context, R.attr.colorSurface))
|
||||
|
||||
|
@ -43,12 +44,12 @@ class LicenseCard
|
|||
val link: String? = a.getString(R.styleable.LicenseCard_link)
|
||||
a.recycle()
|
||||
|
||||
licenseCardName.text = name
|
||||
licenseCardLicense.text = license
|
||||
binding.licenseCardName.text = name
|
||||
binding.licenseCardLicense.text = license
|
||||
if(link.isNullOrBlank()) {
|
||||
licenseCardLink.hide()
|
||||
binding.licenseCardLink.hide()
|
||||
} else {
|
||||
licenseCardLink.text = link
|
||||
binding.licenseCardLink.text = link
|
||||
setOnClickListener { LinkHelper.openLink(link, context) }
|
||||
}
|
||||
|
||||
|
|
|
@ -3,29 +3,24 @@
|
|||
package com.keylesspalace.tusky.view
|
||||
|
||||
import android.app.Activity
|
||||
import android.widget.CheckBox
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.DialogMuteAccountBinding
|
||||
|
||||
fun showMuteAccountDialog(
|
||||
activity: Activity,
|
||||
accountUsername: String,
|
||||
onOk: (notifications: Boolean, duration: Int) -> Unit
|
||||
) {
|
||||
val view = activity.layoutInflater.inflate(R.layout.dialog_mute_account, null)
|
||||
(view.findViewById(R.id.warning) as TextView).text =
|
||||
activity.getString(R.string.dialog_mute_warning, accountUsername)
|
||||
val checkbox: CheckBox = view.findViewById(R.id.checkbox)
|
||||
checkbox.isChecked = true
|
||||
val binding = DialogMuteAccountBinding.inflate(activity.layoutInflater)
|
||||
binding.warning.text = activity.getString(R.string.dialog_mute_warning, accountUsername)
|
||||
binding.checkbox.isChecked = true
|
||||
|
||||
AlertDialog.Builder(activity)
|
||||
.setView(view)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val spinner: Spinner = view.findViewById(R.id.duration)
|
||||
val durationValues = activity.resources.getIntArray(R.array.mute_duration_values)
|
||||
onOk(checkbox.isChecked, durationValues[spinner.selectedItemPosition])
|
||||
onOk(binding.checkbox.isChecked, durationValues[binding.duration.selectedItemPosition])
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.keylesspalace.tusky.viewdata
|
|||
import android.os.Parcelable
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class AttachmentViewData(
|
||||
|
|
|
@ -475,6 +475,7 @@ public abstract class StatusViewData {
|
|||
application = viewData.application;
|
||||
statusEmojis = viewData.getStatusEmojis();
|
||||
accountEmojis = viewData.getAccountEmojis();
|
||||
rebloggedByAccountEmojis = viewData.getRebloggedByAccountEmojis();
|
||||
card = viewData.getCard();
|
||||
isCollapsible = viewData.isCollapsible();
|
||||
isCollapsed = viewData.isCollapsed();
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
android:layout_height="match_parent"
|
||||
tools:context="com.keylesspalace.tusky.AboutActivity">
|
||||
|
||||
<include layout="@layout/toolbar_basic" />
|
||||
<include
|
||||
android:id="@+id/includedToolbar"
|
||||
layout="@layout/toolbar_basic" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
app:layout_constraintStart_toEndOf="@id/accountMuteButton"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Follow Requested" />
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/accountSubscribeButton"
|
||||
style="@style/TuskyButton.Outlined"
|
||||
|
@ -248,20 +248,63 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/accountFieldList"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ViewStub
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/accountMovedView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inflatedId="@+id/accountMovedViewLayout"
|
||||
android:layout="@layout/view_account_moved"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountRemoveView" />
|
||||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="accountMovedText,accountMovedAvatar,accountMovedDisplayName,accountMovedUsername" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrierRemote"
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@+id/accountMovedText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="accountMovedView,accountMovedViewLayout" />
|
||||
android:layout_marginTop="12dp"
|
||||
android:drawablePadding="6dp"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountRemoveView"
|
||||
tools:text="Account has moved" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/accountMovedAvatar"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountMovedText"
|
||||
tools:src="@drawable/avatar_default" />
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@+id/accountMovedDisplayName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="?attr/status_text_large"
|
||||
android:textStyle="normal|bold"
|
||||
app:layout_constraintBottom_toTopOf="@id/accountMovedUsername"
|
||||
app:layout_constraintStart_toEndOf="@id/accountMovedAvatar"
|
||||
app:layout_constraintTop_toTopOf="@id/accountMovedAvatar"
|
||||
tools:text="Display name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/accountMovedUsername"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintBottom_toBottomOf="@id/accountMovedAvatar"
|
||||
app:layout_constraintStart_toEndOf="@id/accountMovedAvatar"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountMovedDisplayName"
|
||||
tools:text="\@username" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/accountStatuses"
|
||||
|
@ -272,7 +315,7 @@
|
|||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toStartOf="@id/accountFollowing"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrierRemote">
|
||||
app:layout_constraintTop_toBottomOf="@id/accountMovedAvatar">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/accountStatusesTextView"
|
||||
|
@ -303,7 +346,7 @@
|
|||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toStartOf="@id/accountFollowers"
|
||||
app:layout_constraintStart_toEndOf="@id/accountStatuses"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrierRemote">
|
||||
app:layout_constraintTop_toBottomOf="@id/accountMovedAvatar">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/accountFollowingTextView"
|
||||
|
@ -333,7 +376,7 @@
|
|||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/accountFollowing"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrierRemote">
|
||||
app:layout_constraintTop_toBottomOf="@id/accountMovedAvatar">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/accountFollowersTextView"
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/activity_view_thread"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.keylesspalace.tusky.AccountListActivity">
|
||||
|
||||
<include layout="@layout/toolbar_basic" />
|
||||
<include
|
||||
android:id="@+id/includedToolbar"
|
||||
layout="@layout/toolbar_basic" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment_container"
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include layout="@layout/toolbar_basic" />
|
||||
<include
|
||||
android:id="@+id/includedToolbar"
|
||||
layout="@layout/toolbar_basic" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue