diff --git a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt index 48095425..ada7af36 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt @@ -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() - } diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index 397b7f64..57f38475 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -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(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 } diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt index d592f053..71118501 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt @@ -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,13 @@ 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) - setSupportActionBar(toolbar) + setSupportActionBar(binding.includedToolbar.toolbar) supportActionBar?.apply { when (type) { Type.BLOCKS -> setTitle(R.string.title_blocks) diff --git a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt index 64d952b9..3d7e0380 100644 --- a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt @@ -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() } diff --git a/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt b/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt index 0726b26e..7e91db07 100644 --- a/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt @@ -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 - 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> { override fun onResponse(call: Call>, response: Response>) { @@ -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>, 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) - } - } - } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt b/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt index d6cc7bca..406a4aaf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt @@ -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() - } - } diff --git a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt index fa3c92c3..04311a66 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt @@ -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 - 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() { override fun areItemsTheSame(oldItem: MastoList, newItem: MastoList): Boolean { return oldItem.id == newItem.id @@ -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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt index 1eebcf69..2ba79798 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt @@ -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, response: Response) { 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, 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, 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 } } diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 3b3af8ae..7cbbf0c7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -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 @@ -179,21 +181,21 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } } 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 +251,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 +266,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 +321,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 +335,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 +371,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } }) - mainDrawer.apply { + binding.mainDrawer.apply { tintStatusBar = true addItems( primaryDrawerItem { @@ -464,7 +466,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 +480,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } if (BuildConfig.DEBUG) { - mainDrawer.addItems( + binding.mainDrawer.addItems( secondaryDrawerItem { nameText = "debug" isEnabled = false @@ -490,7 +492,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 +500,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 +535,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 +550,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 +566,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() } @@ -659,7 +661,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje 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) { + if (me.locked && binding.mainDrawer.getDrawerItem(DRAWER_ITEM_FOLLOW_REQUESTS) == null) { val followRequestsItem = primaryDrawerItem { identifier = DRAWER_ITEM_FOLLOW_REQUESTS nameRes = R.string.action_view_follow_requests @@ -670,9 +672,9 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje startActivityWithSlideInAnimation(intent) } } - mainDrawer.addItemAtPosition(4, followRequestsItem) + binding.mainDrawer.addItemAtPosition(4, followRequestsItem) } else if (!me.locked) { - mainDrawer.removeItems(DRAWER_ITEM_FOLLOW_REQUESTS) + binding.mainDrawer.removeItems(DRAWER_ITEM_FOLLOW_REQUESTS) } updateProfiles() updateShortcut(this, accountManager.activeAccount!!) @@ -695,16 +697,16 @@ 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?) { - 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 +728,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,7 +781,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } - override fun getActionButton(): FloatingActionButton? = composeButton + override fun getActionButton(): FloatingActionButton? = binding.composeButton override fun androidInjector() = androidInjector diff --git a/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt index c3017b0c..64c22917 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt @@ -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 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 + } + + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt index 9eba5bbe..b2691ee9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt @@ -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 diff --git a/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt b/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt index 2b61f141..2fd59902 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt @@ -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 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 { @@ -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 = getString(R.string.max_tab_number_reached, 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) diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt index 5d90bd94..4d5a124c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt @@ -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,8 @@ 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, 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 - } - } + private val binding by viewBinding(ActivityViewMediaBinding::inflate) var isToolbarVisible = true private set @@ -102,7 +84,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 +107,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 +138,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 +147,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 +174,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 +196,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 +212,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 +238,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 +262,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 = @@ -312,14 +294,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 +324,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, 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) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt index ffd97191..9f61bea8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt @@ -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) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index f3c00bde..26e1d5d9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -61,6 +61,7 @@ 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 @@ -76,7 +77,6 @@ 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 java.io.File import java.io.IOException import java.util.* @@ -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,52 +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() } - composeScheduleView.setListener(this) - 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) @@ -388,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]) @@ -447,7 +450,7 @@ class ComposeActivity : BaseActivity(), } // Keep the same text (including insertions) selected - composeEditField.setSelection(start, newEnd) + binding.composeEditField.setSelection(start, newEnd) } } @@ -466,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() @@ -478,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 @@ -529,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() { @@ -545,7 +548,7 @@ class ComposeActivity : BaseActivity(), private fun onScheduleClick() { if (viewModel.scheduledAt.value == null) { - composeScheduleView.openPickDateDialog() + binding.composeScheduleView.openPickDateDialog() } else { showScheduleView() } @@ -563,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() @@ -626,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) @@ -647,7 +650,7 @@ class ComposeActivity : BaseActivity(), private fun removePoll() { viewModel.poll.value = null - pollPreview.hide() + binding.pollPreview.hide() } override fun onVisibilityChanged(visibility: Status.Visibility) { @@ -658,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() { @@ -725,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()) { @@ -747,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) } } @@ -758,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 { } @@ -813,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) { @@ -879,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 { @@ -939,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) @@ -974,8 +976,8 @@ class ComposeActivity : BaseActivity(), private fun setEmojiList(emojiList: List?) { 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()) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt index ca04f9c7..7253112a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt @@ -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) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt index 7bb8766a..f1a07615 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt @@ -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) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt index 2c7f2d46..57c9214c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt @@ -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 { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt index 40a67a4e..4af5771c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt @@ -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() } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt index be705637..7208b038 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt @@ -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() diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt b/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt new file mode 100644 index 00000000..9558b03f --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt @@ -0,0 +1,15 @@ +package com.keylesspalace.tusky.util + +import android.view.LayoutInflater +import androidx.appcompat.app.AppCompatActivity +import androidx.viewbinding.ViewBinding + +/** + * https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c + */ + +inline fun AppCompatActivity.viewBinding( + crossinline bindingInflater: (LayoutInflater) -> T +) = lazy(LazyThreadSafetyMode.NONE) { + bindingInflater(layoutInflater) +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml index 2d55b693..43c7f9ae 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/app/src/main/res/layout/activity_about.xml @@ -6,7 +6,9 @@ android:layout_height="match_parent" tools:context="com.keylesspalace.tusky.AboutActivity"> - + - + - + android:visibility="gone" + app:constraint_referenced_ids="accountMovedText,accountMovedAvatar,accountMovedDisplayName,accountMovedUsername" /> - + 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" /> + + + + + + + app:layout_constraintTop_toBottomOf="@id/accountMovedAvatar"> + app:layout_constraintTop_toBottomOf="@id/accountMovedAvatar"> + app:layout_constraintTop_toBottomOf="@id/accountMovedAvatar"> - + - + - + - + + tools:context=".LicenseActivity"> - + - + + app:layout_constraintTop_toBottomOf="@id/includedToolbar" /> diff --git a/app/src/main/res/layout/activity_modal_timeline.xml b/app/src/main/res/layout/activity_modal_timeline.xml index 6801d904..05de634d 100644 --- a/app/src/main/res/layout/activity_modal_timeline.xml +++ b/app/src/main/res/layout/activity_modal_timeline.xml @@ -2,12 +2,13 @@ - + - + - + + tools:context=".components.scheduled.ScheduledTootActivity"> - + - + - + diff --git a/app/src/main/res/layout/view_account_moved.xml b/app/src/main/res/layout/view_account_moved.xml deleted file mode 100644 index fbbfe48e..00000000 --- a/app/src/main/res/layout/view_account_moved.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file