and though bugs are the bane of my existence, rest assured the wretched thing will get the best of care here

...
 
Commits (3)
......@@ -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()
}
......@@ -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)
......
......@@ -38,6 +38,7 @@ import com.bumptech.glide.load.resource.bitmap.FitCenter
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.adapter.AccountFieldEditAdapter
import com.keylesspalace.tusky.databinding.ActivityEditProfileBinding
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.util.*
......@@ -47,8 +48,6 @@ import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import com.theartofdev.edmodo.cropper.CropImage
import kotlinx.android.synthetic.main.activity_edit_profile.*
import kotlinx.android.synthetic.main.toolbar_basic.*
import javax.inject.Inject
class EditProfileActivity : BaseActivity(), Injectable {
......@@ -71,6 +70,8 @@ class EditProfileActivity : BaseActivity(), Injectable {
private val viewModel: EditProfileViewModel by viewModels { viewModelFactory }
private val binding by viewBinding(ActivityEditProfileBinding::inflate)
private var currentlyPicking: PickType = PickType.NOTHING
private val accountFieldEditAdapter = AccountFieldEditAdapter()
......@@ -88,33 +89,33 @@ class EditProfileActivity : BaseActivity(), Injectable {
currentlyPicking = PickType.valueOf(it)
}
setContentView(R.layout.activity_edit_profile)
setContentView(binding.root)
setSupportActionBar(toolbar)
setSupportActionBar(binding.includedToolbar.toolbar)
supportActionBar?.run {
setTitle(R.string.title_edit_profile)
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
avatarButton.setOnClickListener { onMediaPick(PickType.AVATAR) }
headerButton.setOnClickListener { onMediaPick(PickType.HEADER) }
binding.avatarButton.setOnClickListener { onMediaPick(PickType.AVATAR) }
binding.headerButton.setOnClickListener { onMediaPick(PickType.HEADER) }
fieldList.layoutManager = LinearLayoutManager(this)
fieldList.adapter = accountFieldEditAdapter
binding.fieldList.layoutManager = LinearLayoutManager(this)
binding.fieldList.adapter = accountFieldEditAdapter
val plusDrawable = IconicsDrawable(this, GoogleMaterial.Icon.gmd_add).apply { sizeDp = 12; colorInt = Color.WHITE }
addFieldButton.setCompoundDrawablesRelativeWithIntrinsicBounds(plusDrawable, null, null, null)
binding.addFieldButton.setCompoundDrawablesRelativeWithIntrinsicBounds(plusDrawable, null, null, null)
addFieldButton.setOnClickListener {
binding.addFieldButton.setOnClickListener {
accountFieldEditAdapter.addField()
if(accountFieldEditAdapter.itemCount >= MAX_ACCOUNT_FIELDS) {
it.isVisible = false
}
scrollView.post{
scrollView.smoothScrollTo(0, it.bottom)
binding.scrollView.post{
binding.scrollView.smoothScrollTo(0, it.bottom)
}
}
......@@ -126,12 +127,12 @@ class EditProfileActivity : BaseActivity(), Injectable {
val me = profileRes.data
if (me != null) {
displayNameEditText.setText(me.displayName)
noteEditText.setText(me.source?.note)
lockedCheckBox.isChecked = me.locked
binding.displayNameEditText.setText(me.displayName)
binding.noteEditText.setText(me.source?.note)
binding.lockedCheckBox.isChecked = me.locked
accountFieldEditAdapter.setFields(me.source?.fields ?: emptyList())
addFieldButton.isEnabled = me.source?.fields?.size ?: 0 < MAX_ACCOUNT_FIELDS
binding.addFieldButton.isEnabled = me.source?.fields?.size ?: 0 < MAX_ACCOUNT_FIELDS
if(viewModel.avatarData.value == null) {
Glide.with(this)
......@@ -141,19 +142,19 @@ class EditProfileActivity : BaseActivity(), Injectable {
FitCenter(),
RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp))
)
.into(avatarPreview)
.into(binding.avatarPreview)
}
if(viewModel.headerData.value == null) {
Glide.with(this)
.load(me.header)
.into(headerPreview)
.into(binding.headerPreview)
}
}
}
is Error -> {
val snackbar = Snackbar.make(avatarButton, R.string.error_generic, Snackbar.LENGTH_LONG)
val snackbar = Snackbar.make(binding.avatarButton, R.string.error_generic, Snackbar.LENGTH_LONG)
snackbar.setAction(R.string.action_retry) {
viewModel.obtainProfile()
}
......@@ -169,14 +170,14 @@ class EditProfileActivity : BaseActivity(), Injectable {
is Success -> {
val instance = result.data
if (instance?.maxBioChars != null && instance.maxBioChars > 0) {
noteEditTextLayout.counterMaxLength = instance.maxBioChars
binding.noteEditTextLayout.counterMaxLength = instance.maxBioChars
}
}
}
}
observeImage(viewModel.avatarData, avatarPreview, avatarProgressBar, true)
observeImage(viewModel.headerData, headerPreview, headerProgressBar, false)
observeImage(viewModel.avatarData, binding.avatarPreview, binding.avatarProgressBar, true)
observeImage(viewModel.headerData, binding.headerPreview, binding.headerProgressBar, false)
viewModel.saveData.observe(this, {
when(it) {
......@@ -184,7 +185,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
finish()
}
is Loading -> {
saveProgressBar.visibility = View.VISIBLE
binding.saveProgressBar.visibility = View.VISIBLE
}
is Error -> {
onSaveFailure(it.errorMessage)
......@@ -202,9 +203,9 @@ class EditProfileActivity : BaseActivity(), Injectable {
override fun onStop() {
super.onStop()
if(!isFinishing) {
viewModel.updateProfile(displayNameEditText.text.toString(),
noteEditText.text.toString(),
lockedCheckBox.isChecked,
viewModel.updateProfile(binding.displayNameEditText.text.toString(),
binding.noteEditText.text.toString(),
binding.lockedCheckBox.isChecked,
accountFieldEditAdapter.getFieldData())
}
}
......@@ -268,7 +269,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
initiateMediaPicking()
} else {
endMediaPicking()
Snackbar.make(avatarButton, R.string.error_media_upload_permission, Snackbar.LENGTH_LONG).show()
Snackbar.make(binding.avatarButton, R.string.error_media_upload_permission, Snackbar.LENGTH_LONG).show()
}
}
}
......@@ -309,39 +310,38 @@ class EditProfileActivity : BaseActivity(), Injectable {
return
}
viewModel.save(displayNameEditText.text.toString(),
noteEditText.text.toString(),
lockedCheckBox.isChecked,
viewModel.save(binding.displayNameEditText.text.toString(),
binding.noteEditText.text.toString(),
binding.lockedCheckBox.isChecked,
accountFieldEditAdapter.getFieldData(),
this)
}
private fun onSaveFailure(msg: String?) {
val errorMsg = msg ?: getString(R.string.error_media_upload_sending)
Snackbar.make(avatarButton, errorMsg, Snackbar.LENGTH_LONG).show()
saveProgressBar.visibility = View.GONE
Snackbar.make(binding.avatarButton, errorMsg, Snackbar.LENGTH_LONG).show()
binding.saveProgressBar.visibility = View.GONE
}
private fun beginMediaPicking() {
when (currentlyPicking) {
PickType.AVATAR -> {
avatarProgressBar.visibility = View.VISIBLE
avatarPreview.visibility = View.INVISIBLE
avatarButton.setImageDrawable(null)
binding.avatarProgressBar.visibility = View.VISIBLE
binding.avatarPreview.visibility = View.INVISIBLE
binding.avatarButton.setImageDrawable(null)
}
PickType.HEADER -> {
headerProgressBar.visibility = View.VISIBLE
headerPreview.visibility = View.INVISIBLE
headerButton.setImageDrawable(null)
binding.headerProgressBar.visibility = View.VISIBLE
binding.headerPreview.visibility = View.INVISIBLE
binding.headerButton.setImageDrawable(null)
}
PickType.NOTHING -> { /* do nothing */ }
}
}
private fun endMediaPicking() {
avatarProgressBar.visibility = View.GONE
headerProgressBar.visibility = View.GONE
binding.avatarProgressBar.visibility = View.GONE
binding.headerProgressBar.visibility = View.GONE
currentlyPicking = PickType.NOTHING
}
......@@ -402,7 +402,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
}
private fun onResizeFailure() {
Snackbar.make(avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG).show()
Snackbar.make(binding.avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG).show()
endMediaPicking()
}
......
......@@ -7,13 +7,13 @@ import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
import com.keylesspalace.tusky.databinding.ActivityFiltersBinding
import com.keylesspalace.tusky.databinding.DialogFilterBinding
import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show
import kotlinx.android.synthetic.main.activity_filters.*
import kotlinx.android.synthetic.main.dialog_filter.*
import kotlinx.android.synthetic.main.toolbar_basic.*
import com.keylesspalace.tusky.util.viewBinding
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.Callback
......@@ -28,13 +28,28 @@ class FiltersActivity: BaseActivity() {
@Inject
lateinit var eventHub: EventHub
private val binding by viewBinding(ActivityFiltersBinding::inflate)
private lateinit var context : String
private lateinit var filters: MutableList<Filter>
private lateinit var dialog: AlertDialog
companion object {
const val FILTERS_CONTEXT = "filters_context"
const val FILTERS_TITLE = "filters_title"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
setSupportActionBar(binding.includedToolbar.toolbar)
supportActionBar?.run {
// Back button
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
binding.addFilterButton.setOnClickListener {
showAddFilterDialog()
}
title = intent?.getStringExtra(FILTERS_TITLE)
context = intent?.getStringExtra(FILTERS_CONTEXT)!!
loadFilters()
}
private fun updateFilter(filter: Filter, itemIndex: Int) {
......@@ -101,52 +116,51 @@ class FiltersActivity: BaseActivity() {
}
private fun showAddFilterDialog() {
dialog = AlertDialog.Builder(this@FiltersActivity)
val binding = DialogFilterBinding.inflate(layoutInflater)
binding.phraseWholeWord.isChecked = true
AlertDialog.Builder(this@FiltersActivity)
.setTitle(R.string.filter_addition_dialog_title)
.setView(R.layout.dialog_filter)
.setView(binding.root)
.setPositiveButton(android.R.string.ok){ _, _ ->
createFilter(dialog.phraseEditText.text.toString(), dialog.phraseWholeWord.isChecked)
createFilter(binding.phraseEditText.text.toString(), binding.phraseWholeWord.isChecked)
}
.setNeutralButton(android.R.string.cancel, null)
.create()
dialog.show()
dialog.phraseWholeWord.isChecked = true
.show()
}
private fun setupEditDialogForItem(itemIndex: Int) {
dialog = AlertDialog.Builder(this@FiltersActivity)
val binding = DialogFilterBinding.inflate(layoutInflater)
val filter = filters[itemIndex]
binding.phraseEditText.setText(filter.phrase)
binding.phraseWholeWord.isChecked = filter.wholeWord
AlertDialog.Builder(this@FiltersActivity)
.setTitle(R.string.filter_edit_dialog_title)
.setView(R.layout.dialog_filter)
.setView(binding.root)
.setPositiveButton(R.string.filter_dialog_update_button) { _, _ ->
val oldFilter = filters[itemIndex]
val newFilter = Filter(oldFilter.id, dialog.phraseEditText.text.toString(), oldFilter.context,
oldFilter.expiresAt, oldFilter.irreversible, dialog.phraseWholeWord.isChecked)
val newFilter = Filter(oldFilter.id, binding.phraseEditText.text.toString(), oldFilter.context,
oldFilter.expiresAt, oldFilter.irreversible, binding.phraseWholeWord.isChecked)
updateFilter(newFilter, itemIndex)
}
.setNegativeButton(R.string.filter_dialog_remove_button) { _, _ ->
deleteFilter(itemIndex)
}
.setNeutralButton(android.R.string.cancel, null)
.create()
dialog.show()
// Need to show the dialog before referencing any elements from its view
val filter = filters[itemIndex]
dialog.phraseEditText.setText(filter.phrase)
dialog.phraseWholeWord.isChecked = filter.wholeWord
.show()
}
private fun refreshFilterDisplay() {
filtersView.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, filters.map { filter -> filter.phrase })
filtersView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ -> setupEditDialogForItem(position) }
binding.filtersView.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, filters.map { filter -> filter.phrase })
binding.filtersView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ -> setupEditDialogForItem(position) }
}
private fun loadFilters() {
filterMessageView.hide()
filtersView.hide()
addFilterButton.hide()
filterProgressBar.show()
binding.filterMessageView.hide()
binding.filtersView.hide()
binding.addFilterButton.hide()
binding.filterProgressBar.show()
api.getFilters().enqueue(object : Callback<List<Filter>> {
override fun onResponse(call: Call<List<Filter>>, response: Response<List<Filter>>) {
......@@ -156,52 +170,33 @@ class FiltersActivity: BaseActivity() {
filters = filterResponse.filter { filter -> filter.context.contains(context) }.toMutableList()
refreshFilterDisplay()
filtersView.show()
addFilterButton.show()
filterProgressBar.hide()
binding.filtersView.show()
binding.addFilterButton.show()
binding.filterProgressBar.hide()
} else {
filterProgressBar.hide()
filterMessageView.show()
filterMessageView.setup(R.drawable.elephant_error,
binding.filterProgressBar.hide()
binding.filterMessageView.show()
binding.filterMessageView.setup(R.drawable.elephant_error,
R.string.error_generic) { loadFilters() }
}
}
override fun onFailure(call: Call<List<Filter>>, t: Throwable) {
filterProgressBar.hide()
filterMessageView.show()
binding.filterProgressBar.hide()
binding.filterMessageView.show()
if (t is IOException) {
filterMessageView.setup(R.drawable.elephant_offline,
binding.filterMessageView.setup(R.drawable.elephant_offline,
R.string.error_network) { loadFilters() }
} else {
filterMessageView.setup(R.drawable.elephant_error,
binding.filterMessageView.setup(R.drawable.elephant_error,
R.string.error_generic) { loadFilters() }
}
}
})
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_filters)
setupToolbarBackArrow()
addFilterButton.setOnClickListener {
showAddFilterDialog()
}
title = intent?.getStringExtra(FILTERS_TITLE)
context = intent?.getStringExtra(FILTERS_CONTEXT)!!
loadFilters()
}
private fun setupToolbarBackArrow() {
setSupportActionBar(toolbar)
supportActionBar?.run {
// Back button
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
companion object {
const val FILTERS_CONTEXT = "filters_context"
const val FILTERS_TITLE = "filters_title"
}
}
\ No newline at end of file
......@@ -19,23 +19,20 @@ import android.os.Bundle
import androidx.annotation.RawRes
import android.util.Log
import android.widget.TextView
import com.keylesspalace.tusky.databinding.ActivityLicenseBinding
import com.keylesspalace.tusky.util.IOUtils
import kotlinx.android.extensions.CacheImplementation
import kotlinx.android.extensions.ContainerOptions
import kotlinx.android.synthetic.main.activity_license.*
import kotlinx.android.synthetic.main.toolbar_basic.*
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
class LicenseActivity : BaseActivity() {
@ContainerOptions(cache = CacheImplementation.NO_CACHE)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_license)
val binding = ActivityLicenseBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(toolbar)
setSupportActionBar(binding.includedToolbar.toolbar)
supportActionBar?.run {
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
......@@ -43,7 +40,7 @@ class LicenseActivity : BaseActivity() {
setTitle(R.string.title_licenses)
loadFileIntoTextView(R.raw.apache, licenseApacheTextView)
loadFileIntoTextView(R.raw.apache, binding.licenseApacheTextView)
}
......@@ -67,7 +64,5 @@ class LicenseActivity : BaseActivity() {
IOUtils.closeQuietly(br)
textView.text = sb.toString()
}
}
......@@ -24,12 +24,14 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.activity.viewModels
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.*
import androidx.recyclerview.widget.ListAdapter
import at.connyduck.sparkbutton.helpers.Utils
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.databinding.ActivityListsBinding
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.entity.MastoList
......@@ -47,8 +49,6 @@ import com.uber.autodispose.autoDispose
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.activity_lists.*
import kotlinx.android.synthetic.main.toolbar_basic.*
import javax.inject.Inject
/**
......@@ -57,47 +57,42 @@ import javax.inject.Inject
class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
companion object {
@JvmStatic
fun newIntent(context: Context): Intent {
return Intent(context, ListsActivity::class.java)
}
}
@Inject
lateinit var viewModelFactory: ViewModelFactory
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
private lateinit var viewModel: ListsViewModel
private val viewModel: ListsViewModel by viewModels { viewModelFactory }
private val binding by viewBinding(ActivityListsBinding::inflate)
private val adapter = ListsAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_lists)
setContentView(binding.root)
setSupportActionBar(toolbar)
setSupportActionBar(binding.includedToolbar.toolbar)
supportActionBar?.apply {
title = getString(R.string.title_lists)
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
listsRecycler.adapter = adapter
listsRecycler.layoutManager = LinearLayoutManager(this)
listsRecycler.addItemDecoration(
binding.listsRecycler.adapter = adapter
binding.listsRecycler.layoutManager = LinearLayoutManager(this)
binding.listsRecycler.addItemDecoration(
DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
viewModel = viewModelFactory.create(ListsViewModel::class.java)
viewModel.state
.observeOn(AndroidSchedulers.mainThread())
.autoDispose(from(this))
.subscribe(this::update)
viewModel.retryLoading()
addListButton.setOnClickListener {
binding.addListButton.setOnClickListener {
showlistNameDialog(null)
}
......@@ -153,37 +148,36 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
private fun update(state: ListsViewModel.State) {
adapter.submitList(state.lists)
progressBar.visible(state.loadingState == LOADING)
binding.progressBar.visible(state.loadingState == LOADING)
when (state.loadingState) {
INITIAL, LOADING -> messageView.hide()
INITIAL, LOADING -> binding.messageView.hide()
ERROR_NETWORK -> {
messageView.show()
messageView.setup(R.drawable.elephant_offline, R.string.error_network) {
binding.messageView.show()
binding.messageView.setup(R.drawable.elephant_offline, R.string.error_network) {
viewModel.retryLoading()
}
}
ERROR_OTHER -> {
messageView.show()
messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
binding.messageView.show()
binding.messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
viewModel.retryLoading()
}
}
LOADED ->
if (state.lists.isEmpty()) {
messageView.show()
messageView.setup(R.drawable.elephant_friend_empty, R.string.message_empty,
binding.messageView.show()
binding.messageView.setup(R.drawable.elephant_friend_empty, R.string.message_empty,
null)
} else {
messageView.hide()
binding.messageView.hide()
}
}
}
private fun showMessage(@StringRes messageId: Int) {
Snackbar.make(
listsRecycler, messageId, Snackbar.LENGTH_SHORT
binding.listsRecycler, messageId, Snackbar.LENGTH_SHORT
).show()
}
private fun onListSelected(listId: String) {
......@@ -215,8 +209,6 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
}
}
override fun androidInjector() = dispatchingAndroidInjector
private object ListsDiffer : DiffUtil.ItemCallback<MastoList>() {
override fun areItemsTheSame(oldItem: MastoList, newItem: MastoList): Boolean {
return oldItem.id == newItem.id
......@@ -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
......@@ -29,15 +29,12 @@ import androidx.appcompat.app.AlertDialog
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
import com.bumptech.glide.Glide
import com.keylesspalace.tusky.databinding.ActivityLoginBinding
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.entity.AccessToken
import com.keylesspalace.tusky.entity.AppCredentials
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.ThemeUtils
import com.keylesspalace.tusky.util.getNonNullString
import com.keylesspalace.tusky.util.rickRoll
import com.keylesspalace.tusky.util.shouldRickRoll
import kotlinx.android.synthetic.main.activity_login.*
import com.keylesspalace.tusky.util.*
import okhttp3.HttpUrl
import retrofit2.Call
import retrofit2.Callback
......@@ -49,6 +46,8 @@ class LoginActivity : BaseActivity(), Injectable {
@Inject
lateinit var mastodonApi: MastodonApi
private val binding by viewBinding(ActivityLoginBinding::inflate)
private lateinit var preferences: SharedPreferences
private val oauthRedirectUri: String
......@@ -61,26 +60,26 @@ class LoginActivity : BaseActivity(), Injectable {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
setContentView(binding.root)
if(savedInstanceState == null && BuildConfig.CUSTOM_INSTANCE.isNotBlank() && !isAdditionalLogin()) {
domainEditText.setText(BuildConfig.CUSTOM_INSTANCE)
domainEditText.setSelection(BuildConfig.CUSTOM_INSTANCE.length)
binding.domainEditText.setText(BuildConfig.CUSTOM_INSTANCE)
binding.domainEditText.setSelection(BuildConfig.CUSTOM_INSTANCE.length)
}
if(BuildConfig.CUSTOM_LOGO_URL.isNotBlank()) {
Glide.with(loginLogo)
Glide.with(binding.loginLogo)
.load(BuildConfig.CUSTOM_LOGO_URL)
.placeholder(null)
.into(loginLogo)
.into(binding.loginLogo)
}
preferences = getSharedPreferences(
getString(R.string.preferences_file_key), Context.MODE_PRIVATE)
loginButton.setOnClickListener { onButtonClick() }
binding.loginButton.setOnClickListener { onButtonClick() }
whatsAnInstanceTextView.setOnClickListener {
binding.whatsAnInstanceTextView.setOnClickListener {
val dialog = AlertDialog.Builder(this)
.setMessage(R.string.dialog_whats_an_instance)
.setPositiveButton(R.string.action_close, null)
......@@ -90,11 +89,11 @@ class LoginActivity : BaseActivity(), Injectable {
}
if (isAdditionalLogin()) {
setSupportActionBar(toolbar)
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowTitleEnabled(false)
} else {
toolbar.visibility = View.GONE
binding.toolbar.visibility = View.GONE
}
}
......@@ -117,15 +116,15 @@ class LoginActivity : BaseActivity(), Injectable {
*/
private fun onButtonClick() {
loginButton.isEnabled = false
binding.loginButton.isEnabled = false
val domain = canonicalizeDomain(domainEditText.text.toString())
val domain = canonicalizeDomain(binding.domainEditText.text.toString())
try {
HttpUrl.Builder().host(domain).scheme("https").build()
} catch (e: IllegalArgumentException) {
setLoading(false)
domainTextInputLayout.error = getString(R.string.error_invalid_domain)
binding.domainTextInputLayout.error = getString(R.string.error_invalid_domain)
return
}
......@@ -138,8 +137,8 @@ class LoginActivity : BaseActivity(), Injectable {
override fun onResponse(call: Call<AppCredentials>,
response: Response<AppCredentials>) {
if (!response.isSuccessful) {
loginButton.isEnabled = true
domainTextInputLayout.error = getString(R.string.error_failed_app_registration)
binding.loginButton.isEnabled = true
binding.domainTextInputLayout.error = getString(R.string.error_failed_app_registration)
setLoading(false)
Log.e(TAG, "App authentication failed. " + response.message())
return
......@@ -158,8 +157,8 @@ class LoginActivity : BaseActivity(), Injectable {
}
override fun onFailure(call: Call<AppCredentials>, t: Throwable) {
loginButton.isEnabled = true
domainTextInputLayout.error = getString(R.string.error_failed_app_registration)
binding.loginButton.isEnabled = true
binding.domainTextInputLayout.error = getString(R.string.error_failed_app_registration)
setLoading(false)
Log.e(TAG, Log.getStackTraceString(t))
}
......@@ -190,7 +189,7 @@ class LoginActivity : BaseActivity(), Injectable {
if (viewIntent.resolveActivity(packageManager) != null) {
startActivity(viewIntent)
} else {
domainEditText.error = getString(R.string.error_no_web_browser_found)
binding.domainEditText.error = getString(R.string.error_no_web_browser_found)
setLoading(false)
}
}
......@@ -224,7 +223,7 @@ class LoginActivity : BaseActivity(), Injectable {
onLoginSuccess(response.body()!!.accessToken, domain)
} else {
setLoading(false)
domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
binding.domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
Log.e(TAG, String.format("%s %s",
getString(R.string.error_retrieving_oauth_token),
response.message()))
......@@ -233,7 +232,7 @@ class LoginActivity : BaseActivity(), Injectable {
override fun onFailure(call: Call<AccessToken>, t: Throwable) {
setLoading(false)
domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
binding.domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
Log.e(TAG, String.format("%s %s",
getString(R.string.error_retrieving_oauth_token),
t.message))
......@@ -246,14 +245,14 @@ class LoginActivity : BaseActivity(), Injectable {
/* Authorization failed. Put the error response where the user can read it and they
* can try again. */
setLoading(false)
domainTextInputLayout.error = getString(R.string.error_authorization_denied)
binding.domainTextInputLayout.error = getString(R.string.error_authorization_denied)
Log.e(TAG, String.format("%s %s",
getString(R.string.error_authorization_denied),
error))
} else {
// This case means a junk response was received somehow.
setLoading(false)
domainTextInputLayout.error = getString(R.string.error_authorization_unknown)
binding.domainTextInputLayout.error = getString(R.string.error_authorization_unknown)
}
} else {
// first show or user cancelled login
......@@ -263,12 +262,12 @@ class LoginActivity : BaseActivity(), Injectable {
private fun setLoading(loadingState: Boolean) {
if (loadingState) {
loginLoadingLayout.visibility = View.VISIBLE
loginInputLayout.visibility = View.GONE
binding.loginLoadingLayout.visibility = View.VISIBLE
binding.loginInputLayout.visibility = View.GONE
} else {
loginLoadingLayout.visibility = View.GONE
loginInputLayout.visibility = View.VISIBLE
loginButton.isEnabled = true
binding.loginLoadingLayout.visibility = View.GONE
binding.loginInputLayout.visibility = View.VISIBLE
binding.loginButton.isEnabled = true
}
}
......
......@@ -59,6 +59,7 @@ import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.components.preference.PreferencesActivity
import com.keylesspalace.tusky.components.scheduled.ScheduledTootActivity
import com.keylesspalace.tusky.components.search.SearchActivity
import com.keylesspalace.tusky.databinding.ActivityMainBinding
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.entity.Account
......@@ -86,7 +87,6 @@ import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.activity_main.*
import javax.inject.Inject
class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInjector {
......@@ -108,6 +108,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
@Inject
lateinit var draftHelper: DraftHelper
private val binding by viewBinding(ActivityMainBinding::inflate)
private lateinit var header: AccountHeaderView
private var notificationTabPosition = 0
......@@ -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<in Drawable>?) {
mainToolbar.navigationIcon = FixedSizeDrawable(resource, navIconSize, navIconSize)
binding.mainToolbar.navigationIcon = FixedSizeDrawable(resource, navIconSize, navIconSize)
}
override fun onLoadCleared(placeholder: Drawable?) {
if (placeholder != null) {
mainToolbar.navigationIcon = FixedSizeDrawable(placeholder, navIconSize, navIconSize)
binding.mainToolbar.navigationIcon = FixedSizeDrawable(placeholder, navIconSize, navIconSize)
}
}
})
......@@ -726,7 +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
......
......@@ -4,43 +4,28 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.keylesspalace.tusky.databinding.ActivityModalTimelineBinding
import com.keylesspalace.tusky.fragment.TimelineFragment
import com.keylesspalace.tusky.interfaces.ActionButtonActivity
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
import kotlinx.android.synthetic.main.toolbar_basic.*
import javax.inject.Inject
class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInjector {
companion object {
private const val ARG_KIND = "kind"
private const val ARG_ARG = "arg"
@JvmStatic
fun newIntent(context: Context, kind: TimelineFragment.Kind,
argument: String?): Intent {
val intent = Intent(context, ModalTimelineActivity::class.java)
intent.putExtra(ARG_KIND, kind)
intent.putExtra(ARG_ARG, argument)
return intent
}
}
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_modal_timeline)
setSupportActionBar(toolbar)
val bar = supportActionBar
if (bar != null) {
bar.title = getString(R.string.title_list_timeline)
bar.setDisplayHomeAsUpEnabled(true)
bar.setDisplayShowHomeEnabled(true)
val binding = ActivityModalTimelineBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.includedToolbar.toolbar)
supportActionBar?.apply {
title = getString(R.string.title_list_timeline)
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
if (supportFragmentManager.findFragmentById(R.id.contentFrame) == null) {
......@@ -57,4 +42,18 @@ class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasAn
override fun androidInjector() = dispatchingAndroidInjector
companion object {
private const val ARG_KIND = "kind"
private const val ARG_ARG = "arg"
@JvmStatic
fun newIntent(context: Context, kind: TimelineFragment.Kind,
argument: String?): Intent {
val intent = Intent(context, ModalTimelineActivity::class.java)
intent.putExtra(ARG_KIND, kind)
intent.putExtra(ARG_ARG, argument)
return intent
}
}
}
......@@ -19,6 +19,7 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.commit
import com.keylesspalace.tusky.databinding.ActivityStatuslistBinding
import com.keylesspalace.tusky.fragment.TimelineFragment
import com.keylesspalace.tusky.fragment.TimelineFragment.Kind
......@@ -27,9 +28,6 @@ import javax.inject.Inject
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
import kotlinx.android.extensions.CacheImplementation
import kotlinx.android.extensions.ContainerOptions
import kotlinx.android.synthetic.main.toolbar_basic.*
class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
......@@ -39,12 +37,12 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
private val kind: Kind
get() = Kind.valueOf(intent.getStringExtra(EXTRA_KIND)!!)
@ContainerOptions(cache = CacheImplementation.NO_CACHE)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_statuslist)
val binding = ActivityStatuslistBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(toolbar)
setSupportActionBar(binding.includedToolbar.toolbar)
val title = if(kind == Kind.FAVOURITES) {
R.string.title_favourites
......
......@@ -38,17 +38,17 @@ import com.keylesspalace.tusky.adapter.ListSelectionAdapter
import com.keylesspalace.tusky.adapter.TabAdapter
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.MainTabsChangedEvent
import com.keylesspalace.tusky.databinding.ActivityTabPreferenceBinding
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.onTextChanged
import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.util.visible
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
import com.uber.autodispose.autoDispose
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.activity_tab_preference.*
import kotlinx.android.synthetic.main.toolbar_basic.*
import java.util.regex.Pattern
import javax.inject.Inject
......@@ -59,6 +59,8 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
@Inject
lateinit var eventHub: EventHub
private val binding by viewBinding(ActivityTabPreferenceBinding::inflate)
private lateinit var currentTabs: MutableList<TabData>
private lateinit var currentTabsAdapter: TabAdapter
private lateinit var touchHelper: ItemTouchHelper
......@@ -73,9 +75,9 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tab_preference)
setContentView(binding.root)
setSupportActionBar(toolbar)
setSupportActionBar(binding.includedToolbar.toolbar)
supportActionBar?.apply {
setTitle(R.string.title_tab_preferences)
......@@ -85,13 +87,13 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
currentTabs = accountManager.activeAccount?.tabPreferences.orEmpty().toMutableList()
currentTabsAdapter = TabAdapter(currentTabs, false, this, currentTabs.size <= MIN_TAB_COUNT)
currentTabsRecyclerView.adapter = currentTabsAdapter
currentTabsRecyclerView.layoutManager = LinearLayoutManager(this)
currentTabsRecyclerView.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
binding.currentTabsRecyclerView.adapter = currentTabsAdapter
binding.currentTabsRecyclerView.layoutManager = LinearLayoutManager(this)
binding.currentTabsRecyclerView.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
addTabAdapter = TabAdapter(listOf(createTabDataFromId(DIRECT)), true, this)
addTabRecyclerView.adapter = addTabAdapter
addTabRecyclerView.layoutManager = LinearLayoutManager(this)
binding.addTabRecyclerView.adapter = addTabAdapter
binding.addTabRecyclerView.layoutManager = LinearLayoutManager(this)
touchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
......@@ -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)
......
......@@ -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<AttachmentViewData>, index: Int): Intent {
val intent = Intent(context, ViewMediaActivity::class.java)
intent.putParcelableArrayListExtra(EXTRA_ATTACHMENTS, ArrayList(attachments))
intent.putExtra(EXTRA_ATTACHMENT_INDEX, index)
return intent
}
@JvmStatic
fun newSingleImageIntent(context: Context, url: String): Intent {
val intent = Intent(context, ViewMediaActivity::class.java)
intent.putExtra(EXTRA_SINGLE_IMAGE_URL, url)
return intent
}
}
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<Bitmap> =
......@@ -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<AttachmentViewData>, index: Int): Intent {
val intent = Intent(context, ViewMediaActivity::class.java)
intent.putParcelableArrayListExtra(EXTRA_ATTACHMENTS, ArrayList(attachments))
intent.putExtra(EXTRA_ATTACHMENT_INDEX, index)
return intent
}
@JvmStatic
fun newSingleImageIntent(context: Context, url: String): Intent {
val intent = Intent(context, ViewMediaActivity::class.java)
intent.putExtra(EXTRA_SINGLE_IMAGE_URL, url)
return intent
}
}
}
abstract class ViewMediaAdapter(activity: FragmentActivity): FragmentStateAdapter(activity) {
......
......@@ -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) {
......
......@@ -23,8 +23,8 @@ import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.AddPollOptionsAdapter
import com.keylesspalace.tusky.databinding.DialogAddPollBinding
import com.keylesspalace.tusky.entity.NewPoll
import kotlinx.android.synthetic.main.dialog_add_poll.view.*
fun showAddPollDialog(
context: Context,
......@@ -34,12 +34,12 @@ fun showAddPollDialog(
onUpdatePoll: (NewPoll) -> Unit
) {
val view = LayoutInflater.from(context).inflate(R.layout.dialog_add_poll, null)
val binding = DialogAddPollBinding.inflate(LayoutInflater.from(context))
val dialog = AlertDialog.Builder(context)
.setIcon(R.drawable.ic_poll_24dp)
.setTitle(R.string.create_poll_title)
.setView(view)
.setView(binding.root)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, null)
.create()
......@@ -48,7 +48,7 @@ fun showAddPollDialog(
options = poll?.options?.toMutableList() ?: mutableListOf("", ""),
maxOptionLength = maxOptionLength,
onOptionRemoved = { valid ->
view.addChoiceButton.isEnabled = true
binding.addChoiceButton.isEnabled = true
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = valid
},
onOptionChanged = { valid ->
......@@ -56,9 +56,9 @@ fun showAddPollDialog(
}
)
view.pollChoices.adapter = adapter
binding.pollChoices.adapter = adapter
view.addChoiceButton.setOnClickListener {
binding.addChoiceButton.setOnClickListener {
if (adapter.itemCount < maxOptionCount) {
adapter.addChoice()
}
......@@ -71,14 +71,14 @@ fun showAddPollDialog(
it <= poll?.expiresIn ?: 0
}
view.pollDurationSpinner.setSelection(pollDurationId)
binding.pollDurationSpinner.setSelection(pollDurationId)
view.multipleChoicesCheckBox.isChecked = poll?.multiple ?: false
binding.multipleChoicesCheckBox.isChecked = poll?.multiple ?: false
dialog.setOnShowListener {
val button = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
button.setOnClickListener {
val selectedPollDurationId = view.pollDurationSpinner.selectedItemPosition
val selectedPollDurationId = binding.pollDurationSpinner.selectedItemPosition
val pollDuration = context.resources
.getIntArray(R.array.poll_duration_values)[selectedPollDurationId]
......@@ -86,7 +86,7 @@ fun showAddPollDialog(
onUpdatePoll(NewPoll(
options = adapter.pollOptions,
expiresIn = pollDuration,
multiple = view.multipleChoicesCheckBox.isChecked
multiple = binding.multipleChoicesCheckBox.isChecked
))
dialog.dismiss()
......@@ -97,5 +97,4 @@ fun showAddPollDialog(
// make the dialog focusable so the keyboard does not stay behind it
dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
}
\ No newline at end of file
......@@ -17,11 +17,12 @@ package com.keylesspalace.tusky.components.compose.view
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.PreviewPollOptionsAdapter
import com.keylesspalace.tusky.databinding.ViewPollPreviewBinding
import com.keylesspalace.tusky.entity.NewPoll
import kotlinx.android.synthetic.main.view_poll_preview.view.*
class PollPreviewView @JvmOverloads constructor(
context: Context?,
......@@ -29,11 +30,11 @@ class PollPreviewView @JvmOverloads constructor(
defStyleAttr: Int = 0)
: LinearLayout(context, attrs, defStyleAttr) {
val adapter = PreviewPollOptionsAdapter()
private val adapter = PreviewPollOptionsAdapter()
init {
inflate(context, R.layout.view_poll_preview, this)
private val binding = ViewPollPreviewBinding.inflate(LayoutInflater.from(context), this)
init {
orientation = VERTICAL
setBackgroundResource(R.drawable.card_frame)
......@@ -42,8 +43,7 @@ class PollPreviewView @JvmOverloads constructor(
setPadding(padding, padding, padding, padding)
pollPreviewOptions.adapter = adapter
binding.pollPreviewOptions.adapter = adapter
}
fun setPoll(poll: NewPoll){
......@@ -52,13 +52,11 @@ class PollPreviewView @JvmOverloads constructor(
val pollDurationId = resources.getIntArray(R.array.poll_duration_values).indexOfLast {
it <= poll.expiresIn
}
pollDurationPreview.text = resources.getStringArray(R.array.poll_duration_names)[pollDurationId]
binding.pollDurationPreview.text = resources.getStringArray(R.array.poll_duration_names)[pollDurationId]
}
override fun setOnClickListener(l: OnClickListener?) {
super.setOnClickListener(l)
adapter.setOnClickListener(l)
}
}
\ No newline at end of file
......@@ -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)
......
......@@ -8,14 +8,23 @@ import android.os.Build
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.*
import android.widget.RadioButton
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.SplashActivity
import com.keylesspalace.tusky.databinding.DialogEmojicompatBinding
import com.keylesspalace.tusky.databinding.ItemEmojiPrefBinding
import com.keylesspalace.tusky.util.EmojiCompatFont
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.BLOBMOJI
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.FONTS
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.NOTOEMOJI
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.SYSTEM_DEFAULT
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.TWEMOJI
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import okhttp3.OkHttpClient
......@@ -50,94 +59,85 @@ class EmojiPreference(
}
override fun onClick() {
val view = LayoutInflater.from(context).inflate(R.layout.dialog_emojicompat, null)
viewIds.forEachIndexed { index, viewId ->
setupItem(view.findViewById(viewId), FONTS[index])
}
val binding = DialogEmojicompatBinding.inflate(LayoutInflater.from(context))
setupItem(BLOBMOJI, binding.itemBlobmoji)
setupItem(TWEMOJI, binding.itemTwemoji)
setupItem(NOTOEMOJI, binding.itemNotoemoji)
setupItem(SYSTEM_DEFAULT, binding.itemNomoji)
AlertDialog.Builder(context)
.setView(view)
.setView(binding.root)
.setPositiveButton(android.R.string.ok) { _, _ -> onDialogOk() }
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun setupItem(container: View, font: EmojiCompatFont) {
val title: TextView = container.findViewById(R.id.emojicompat_name)
val caption: TextView = container.findViewById(R.id.emojicompat_caption)
val thumb: ImageView = container.findViewById(R.id.emojicompat_thumb)
val download: ImageButton = container.findViewById(R.id.emojicompat_download)
val cancel: ImageButton = container.findViewById(R.id.emojicompat_download_cancel)
val radio: RadioButton = container.findViewById(R.id.emojicompat_radio)
private fun setupItem(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
// Initialize all the views
title.text = font.getDisplay(container.context)
caption.setText(font.caption)
thumb.setImageResource(font.img)
binding.emojiName.text = font.getDisplay(context)
binding.emojiCaption.setText(font.caption)
binding.emojiThumbnail.setImageResource(font.img)
// There needs to be a list of all the radio buttons in order to uncheck them when one is selected
radioButtons.add(radio)
updateItem(font, container)
radioButtons.add(binding.emojiRadioButton)
updateItem(font, binding)
// Set actions
download.setOnClickListener { startDownload(font, container) }
cancel.setOnClickListener { cancelDownload(font, container) }
radio.setOnClickListener { radioButton: View -> select(font, radioButton as RadioButton) }
container.setOnClickListener { containerView: View ->
select(font, containerView.findViewById(R.id.emojicompat_radio))
binding.emojiDownload.setOnClickListener { startDownload(font, binding) }
binding.emojiDownloadCancel.setOnClickListener { cancelDownload(font, binding) }
binding.emojiRadioButton.setOnClickListener { radioButton: View -> select(font, radioButton as RadioButton) }
binding.root.setOnClickListener {
select(font, binding.emojiRadioButton)
}
}
private fun startDownload(font: EmojiCompatFont, container: View) {
val download: ImageButton = container.findViewById(R.id.emojicompat_download)
val caption: TextView = container.findViewById(R.id.emojicompat_caption)
val progressBar: ProgressBar = container.findViewById(R.id.emojicompat_progress)
val cancel: ImageButton = container.findViewById(R.id.emojicompat_download_cancel)
private fun startDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
// Switch to downloading style
download.visibility = View.GONE
caption.visibility = View.INVISIBLE
progressBar.visibility = View.VISIBLE
progressBar.progress = 0
cancel.visibility = View.VISIBLE
binding.emojiDownload.hide()
binding.emojiCaption.visibility = View.INVISIBLE
binding.emojiProgress.show()
binding.emojiProgress.progress = 0
binding.emojiDownloadCancel.show()
font.downloadFontFile(context, okHttpClient)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ progress ->
// The progress is returned as a float between 0 and 1, or -1 if it could not determined
if (progress >= 0) {
progressBar.isIndeterminate = false
val max = progressBar.max.toFloat()
binding.emojiProgress.isIndeterminate = false
val max = binding.emojiProgress.max.toFloat()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
progressBar.setProgress((max * progress).toInt(), true)
binding.emojiProgress.setProgress((max * progress).toInt(), true)
} else {
progressBar.progress = (max * progress).toInt()
binding.emojiProgress.progress = (max * progress).toInt()
}
} else {
progressBar.isIndeterminate = true
binding.emojiProgress.isIndeterminate = true
}
},
{
Toast.makeText(context, R.string.download_failed, Toast.LENGTH_SHORT).show()
updateItem(font, container)
updateItem(font, binding)
},
{
finishDownload(font, container)
finishDownload(font, binding)
}
).also { downloadDisposables[font.id] = it }
}
private fun cancelDownload(font: EmojiCompatFont, container: View) {
font.deleteDownloadedFile(container.context)
private fun cancelDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
font.deleteDownloadedFile(context)
downloadDisposables[font.id]?.dispose()
downloadDisposables[font.id] = null
updateItem(font, container)
updateItem(font, binding)
}
private fun finishDownload(font: EmojiCompatFont, container: View) {
select(font, container.findViewById(R.id.emojicompat_radio))
updateItem(font, container)
private fun finishDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
select(font, binding.emojiRadioButton)
updateItem(font, binding)
// Set the flag to restart the app (because an update has been downloaded)
if (selected === original && currentNeedsUpdate) {
updated = true
......@@ -153,54 +153,43 @@ class EmojiPreference(
*/
private fun select(font: EmojiCompatFont, radio: RadioButton) {
selected = font
// Uncheck all the other buttons
for (other in radioButtons) {
if (other !== radio) {
other.isChecked = false
}
radioButtons.forEach { radioButton ->
radioButton.isChecked = radioButton == radio
}
radio.isChecked = true
}
/**
* Called when a "consistent" state is reached, i.e. it's not downloading the font
*
* @param font The font to be displayed
* @param container The ConstraintLayout containing the item
* @param binding The ItemEmojiPrefBinding to show the item in
*/
private fun updateItem(font: EmojiCompatFont, container: View) {
// Assignments
val download: ImageButton = container.findViewById(R.id.emojicompat_download)
val caption: TextView = container.findViewById(R.id.emojicompat_caption)
val progress: ProgressBar = container.findViewById(R.id.emojicompat_progress)
val cancel: ImageButton = container.findViewById(R.id.emojicompat_download_cancel)
val radio: RadioButton = container.findViewById(R.id.emojicompat_radio)
private fun updateItem(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
// There's no download going on
progress.visibility = View.GONE
cancel.visibility = View.GONE
caption.visibility = View.VISIBLE
binding.emojiProgress.hide()
binding.emojiDownloadCancel.hide()
binding.emojiCaption.show()
if (font.isDownloaded(context)) {
// Make it selectable
download.visibility = View.GONE
radio.visibility = View.VISIBLE
container.isClickable = true
binding.emojiDownload.hide()
binding.emojiRadioButton.show()
binding.root.isClickable = true
} else {
// Make it downloadable
download.visibility = View.VISIBLE
radio.visibility = View.GONE
container.isClickable = false
binding.emojiDownload.show()
binding.emojiRadioButton.hide()
binding.root.isClickable = false
}
// Select it if necessary
if (font === selected) {
radio.isChecked = true
binding.emojiRadioButton.isChecked = true
// Update available
if (!font.isDownloaded(context)) {
currentNeedsUpdate = true
}
} else {
radio.isChecked = false
binding.emojiRadioButton.isChecked = false
}
}
......@@ -246,13 +235,5 @@ class EmojiPreference(
companion object {
private const val TAG = "EmojiPreference"
// Please note that this array must sorted in the same way as the fonts.
private val viewIds = intArrayOf(
R.id.item_nomoji,
R.id.item_blobmoji,
R.id.item_twemoji,
R.id.item_notoemoji
)
}
}
\ No newline at end of file
......@@ -28,12 +28,12 @@ import com.keylesspalace.tusky.MainActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
import com.keylesspalace.tusky.databinding.ActivityPreferencesBinding
import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.util.ThemeUtils
import com.keylesspalace.tusky.util.getNonNullString
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
import kotlinx.android.synthetic.main.toolbar_basic.*
import javax.inject.Inject
class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceChangeListener,
......@@ -48,12 +48,12 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference
private var restartActivitiesOnExit: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_preferences)
val binding = ActivityPreferencesBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(toolbar)
setSupportActionBar(binding.includedToolbar.toolbar)
supportActionBar?.run {
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
......
......@@ -22,11 +22,11 @@ import androidx.activity.viewModels
import com.keylesspalace.tusky.BottomSheetActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.report.adapter.ReportPagerAdapter
import com.keylesspalace.tusky.databinding.ActivityReportBinding
import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.util.viewBinding
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
import kotlinx.android.synthetic.main.activity_report.*
import kotlinx.android.synthetic.main.toolbar_basic.*
import javax.inject.Inject
class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
......@@ -39,6 +39,8 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
private val viewModel: ReportViewModel by viewModels { viewModelFactory }
private val binding by viewBinding(ActivityReportBinding::inflate)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val accountId = intent?.getStringExtra(ACCOUNT_ID)
......@@ -50,9 +52,9 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
viewModel.init(accountId, accountUserName, intent?.getStringExtra(STATUS_ID))
setContentView(R.layout.activity_report)
setContentView(binding.root)
setSupportActionBar(toolbar)
setSupportActionBar(binding.includedToolbar.toolbar)
supportActionBar?.apply {
title = getString(R.string.report_username_format, viewModel.accountUserName)
......@@ -69,8 +71,8 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
}
private fun initViewPager() {
wizard.isUserInputEnabled = false
wizard.adapter = ReportPagerAdapter(this)
binding.wizard.isUserInputEnabled = false
binding.wizard.adapter = ReportPagerAdapter(this)
}
private fun subscribeObservables() {
......@@ -96,18 +98,18 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
}
private fun showPreviousScreen() {
when (wizard.currentItem) {
when (binding.wizard.currentItem) {
0 -> closeScreen()
1 -> showStatusesPage()
}
}
private fun showDonePage() {
wizard.currentItem = 2
binding.wizard.currentItem = 2
}
private fun showNotesPage() {
wizard.currentItem = 1
binding.wizard.currentItem = 1
}
private fun closeScreen() {
......@@ -115,7 +117,7 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
}
private fun showStatusesPage() {
wizard.currentItem = 0
binding.wizard.currentItem = 0
}
companion object {
......
......@@ -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()
}
}
}
......
......@@ -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()
......
......@@ -256,27 +256,27 @@ class EmojiCompatFont(
private const val CHUNK_SIZE = 4096L
// The system font gets some special behavior...
private val SYSTEM_DEFAULT = EmojiCompatFont("system-default",
val SYSTEM_DEFAULT = EmojiCompatFont("system-default",
"System Default",
R.string.caption_systememoji,
R.drawable.ic_emoji_34dp,
"",
"0")
private val BLOBMOJI = EmojiCompatFont("Blobmoji",
val BLOBMOJI = EmojiCompatFont("Blobmoji",
"Blobmoji",
R.string.caption_blobmoji,
R.drawable.ic_blobmoji,
"https://tusky.app/hosted/emoji/BlobmojiCompat.ttf",
"12.0.0"
)
private val TWEMOJI = EmojiCompatFont("Twemoji",
val TWEMOJI = EmojiCompatFont("Twemoji",
"Twemoji",
R.string.caption_twemoji,
R.drawable.ic_twemoji,
"https://tusky.app/hosted/emoji/TwemojiCompat.ttf",
"12.0.0"
)
private val NOTOEMOJI = EmojiCompatFont("NotoEmoji",
val NOTOEMOJI = EmojiCompatFont("NotoEmoji",
"Noto Emoji",
R.string.caption_notoemoji,
R.drawable.ic_notoemoji,
......
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 <T : ViewBinding> AppCompatActivity.viewBinding(
crossinline bindingInflater: (LayoutInflater) -> T
) = lazy(LazyThreadSafetyMode.NONE) {
bindingInflater(layoutInflater)
}
\ No newline at end of file
......@@ -3,14 +3,14 @@ package com.keylesspalace.tusky.view
import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ViewBackgroundMessageBinding
import com.keylesspalace.tusky.util.visible
import kotlinx.android.synthetic.main.view_background_message.view.*
/**
* This view is used for screens with downloadable content which may fail.
......@@ -22,8 +22,9 @@ class BackgroundMessageView @JvmOverloads constructor(
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private val binding = ViewBackgroundMessageBinding.inflate(LayoutInflater.from(context), this)
init {
View.inflate(context, R.layout.view_background_message, this)
gravity = Gravity.CENTER_HORIZONTAL
orientation = VERTICAL
......@@ -36,11 +37,14 @@ class BackgroundMessageView @JvmOverloads constructor(
* Setup image, message and button.
* If [clickListener] is `null` then the button will be hidden.
*/
fun setup(@DrawableRes imageRes: Int, @StringRes messageRes: Int,
clickListener: ((v: View) -> Unit)? = null) {
messageTextView.setText(messageRes)
imageView.setImageResource(imageRes)
button.setOnClickListener(clickListener)
button.visible(clickListener != null)
fun setup(
@DrawableRes imageRes: Int,
@StringRes messageRes: Int,
clickListener: ((v: View) -> Unit)? = null
) {
binding.messageTextView.setText(messageRes)
binding.imageView.setImageResource(imageRes)
binding.button.setOnClickListener(clickListener)
binding.button.visible(clickListener != null)
}
}
\ No newline at end of file
......@@ -17,12 +17,13 @@ package com.keylesspalace.tusky.view
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import com.google.android.material.card.MaterialCardView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.CardLicenseBinding
import com.keylesspalace.tusky.util.LinkHelper
import com.keylesspalace.tusky.util.ThemeUtils
import com.keylesspalace.tusky.util.hide
import kotlinx.android.synthetic.main.card_license.view.*
class LicenseCard
@JvmOverloads constructor(
......@@ -32,7 +33,7 @@ class LicenseCard
) : MaterialCardView(context, attrs, defStyleAttr) {
init {
inflate(context, R.layout.card_license, this)
val binding = CardLicenseBinding.inflate(LayoutInflater.from(context), this)
setCardBackgroundColor(ThemeUtils.getColor(context, R.attr.colorSurface))
......@@ -43,12 +44,12 @@ class LicenseCard
val link: String? = a.getString(R.styleable.LicenseCard_link)
a.recycle()
licenseCardName.text = name
licenseCardLicense.text = license
binding.licenseCardName.text = name
binding.licenseCardLicense.text = license
if(link.isNullOrBlank()) {
licenseCardLink.hide()
binding.licenseCardLink.hide()
} else {
licenseCardLink.text = link
binding.licenseCardLink.text = link
setOnClickListener { LinkHelper.openLink(link, context) }
}
......
......@@ -3,29 +3,24 @@
package com.keylesspalace.tusky.view
import android.app.Activity
import android.widget.CheckBox
import android.widget.Spinner
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.DialogMuteAccountBinding
fun showMuteAccountDialog(
activity: Activity,
accountUsername: String,
onOk: (notifications: Boolean, duration: Int) -> Unit
) {
val view = activity.layoutInflater.inflate(R.layout.dialog_mute_account, null)
(view.findViewById(R.id.warning) as TextView).text =
activity.getString(R.string.dialog_mute_warning, accountUsername)
val checkbox: CheckBox = view.findViewById(R.id.checkbox)
checkbox.isChecked = true
val binding = DialogMuteAccountBinding.inflate(activity.layoutInflater)
binding.warning.text = activity.getString(R.string.dialog_mute_warning, accountUsername)
binding.checkbox.isChecked = true
AlertDialog.Builder(activity)
.setView(view)
.setView(binding.root)
.setPositiveButton(android.R.string.ok) { _, _ ->
val spinner: Spinner = view.findViewById(R.id.duration)
val durationValues = activity.resources.getIntArray(R.array.mute_duration_values)
onOk(checkbox.isChecked, durationValues[spinner.selectedItemPosition])
onOk(binding.checkbox.isChecked, durationValues[binding.duration.selectedItemPosition])
}
.setNegativeButton(android.R.string.cancel, null)
.show()
......
......@@ -6,7 +6,9 @@
android:layout_height="match_parent"
tools:context="com.keylesspalace.tusky.AboutActivity">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<FrameLayout
android:layout_width="match_parent"
......
......@@ -71,7 +71,7 @@
app:layout_constraintStart_toEndOf="@id/accountMuteButton"
app:layout_constraintTop_toTopOf="parent"
tools:text="Follow Requested" />
<com.google.android.material.button.MaterialButton
android:id="@+id/accountSubscribeButton"
style="@style/TuskyButton.Outlined"
......@@ -248,20 +248,63 @@
app:layout_constraintTop_toBottomOf="@id/accountFieldList"
tools:visibility="visible" />
<ViewStub
<androidx.constraintlayout.widget.Group
android:id="@+id/accountMovedView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/accountMovedViewLayout"
android:layout="@layout/view_account_moved"
app:layout_constraintTop_toBottomOf="@id/accountRemoveView" />
android:visibility="gone"
app:constraint_referenced_ids="accountMovedText,accountMovedAvatar,accountMovedDisplayName,accountMovedUsername" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrierRemote"
<androidx.emoji.widget.EmojiTextView
android:id="@+id/accountMovedText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="accountMovedView,accountMovedViewLayout" />
android:layout_marginTop="12dp"
android:drawablePadding="6dp"
android:textSize="?attr/status_text_medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accountRemoveView"
tools:text="Account has moved" />
<ImageView
android:id="@+id/accountMovedAvatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accountMovedText"
tools:src="@drawable/avatar_default" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/accountMovedDisplayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorPrimary"
android:textSize="?attr/status_text_large"
android:textStyle="normal|bold"
app:layout_constraintBottom_toTopOf="@id/accountMovedUsername"
app:layout_constraintStart_toEndOf="@id/accountMovedAvatar"
app:layout_constraintTop_toTopOf="@id/accountMovedAvatar"
tools:text="Display name" />
<TextView
android:id="@+id/accountMovedUsername"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="?attr/status_text_medium"
app:layout_constraintBottom_toBottomOf="@id/accountMovedAvatar"
app:layout_constraintStart_toEndOf="@id/accountMovedAvatar"
app:layout_constraintTop_toBottomOf="@id/accountMovedDisplayName"
tools:text="\@username" />
<LinearLayout
android:id="@+id/accountStatuses"
......@@ -272,7 +315,7 @@
android:orientation="vertical"
app:layout_constraintEnd_toStartOf="@id/accountFollowing"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrierRemote">
app:layout_constraintTop_toBottomOf="@id/accountMovedAvatar">
<TextView
android:id="@+id/accountStatusesTextView"
......@@ -303,7 +346,7 @@
android:orientation="vertical"
app:layout_constraintEnd_toStartOf="@id/accountFollowers"
app:layout_constraintStart_toEndOf="@id/accountStatuses"
app:layout_constraintTop_toBottomOf="@id/barrierRemote">
app:layout_constraintTop_toBottomOf="@id/accountMovedAvatar">
<TextView
android:id="@+id/accountFollowingTextView"
......@@ -333,7 +376,7 @@
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/accountFollowing"
app:layout_constraintTop_toBottomOf="@id/barrierRemote">
app:layout_constraintTop_toBottomOf="@id/accountMovedAvatar">
<TextView
android:id="@+id/accountFollowersTextView"
......
......@@ -2,12 +2,13 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_view_thread"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.keylesspalace.tusky.AccountListActivity">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
......
......@@ -5,7 +5,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<ProgressBar
android:id="@+id/progressBar"
......
......@@ -6,7 +6,9 @@
android:layout_height="match_parent"
tools:context="com.keylesspalace.tusky.EditProfileActivity">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
......
......@@ -2,12 +2,13 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activityFilters"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.keylesspalace.tusky.FiltersActivity">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<ListView
android:id="@+id/filtersView"
......
......@@ -5,9 +5,11 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.keylesspalace.tusky.AboutActivity">
tools:context=".LicenseActivity">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<ScrollView
android:layout_width="match_parent"
......
......@@ -9,7 +9,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/listsRecycler"
......@@ -18,7 +20,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar" />
app:layout_constraintTop_toBottomOf="@id/includedToolbar" />
<ProgressBar
android:id="@+id/progressBar"
......@@ -39,7 +41,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar"
app:layout_constraintTop_toBottomOf="@id/includedToolbar"
tools:visibility="visible"
app:layout_constrainedHeight="true" />
......
......@@ -2,12 +2,13 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_view_thread"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.keylesspalace.tusky.ModalTimelineActivity">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/contentFrame"
......
......@@ -2,12 +2,13 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_view_thread"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.keylesspalace.tusky.components.preference.PreferencesActivity">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
......
......@@ -2,12 +2,13 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_view_thread"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".components.report.ReportActivity">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/wizard"
......
......@@ -2,12 +2,13 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activityScheduledToot"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.keylesspalace.tusky.AccountListActivity">
tools:context=".components.scheduled.ScheduledTootActivity">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
......
......@@ -2,12 +2,13 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_view_thread"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.keylesspalace.tusky.StatusListActivity">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
......
......@@ -2,11 +2,12 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tabPreferenceContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/currentTabsRecyclerView"
......
......@@ -2,7 +2,6 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_view_thread"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.keylesspalace.tusky.ViewTagActivity">
......
......@@ -23,7 +23,6 @@
android:text="@string/dialog_mute_hide_notifications"/>
<TextView
android:id="@+id/duration_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="20dp"
......
......@@ -2,7 +2,6 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/emojicompat_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
......@@ -11,7 +10,7 @@
<!--This is a thumbnail picture-->
<ImageView
android:id="@+id/emojicompat_thumb"
android:id="@+id/emojiThumbnail"
android:layout_width="42dp"
android:layout_height="42dp"
android:layout_marginStart="16dp"
......@@ -25,22 +24,22 @@
<!--This is the font's name-->
<TextView
android:id="@+id/emojicompat_name"
android:id="@+id/emojiName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="72dp"
android:textColor="?android:textColorPrimary"
android:textSize="?attr/status_text_medium"
app:layout_constraintBottom_toTopOf="@+id/emojicompat_caption"
app:layout_constraintBottom_toTopOf="@+id/emojiCaption"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/emojicompat_thumb"
app:layout_constraintStart_toEndOf="@+id/emojiThumbnail"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/system_default" />
<!--A short caption…-->
<TextView
android:id="@+id/emojicompat_caption"
android:id="@+id/emojiCaption"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="72dp"
......@@ -49,14 +48,14 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="@id/emojicompat_name"
app:layout_constraintTop_toBottomOf="@id/emojicompat_name"
app:layout_constraintStart_toStartOf="@id/emojiName"
app:layout_constraintTop_toBottomOf="@id/emojiName"
app:layout_constraintVertical_chainStyle="packed"
tools:text="@string/caption_blobmoji" />
<!--This progress bar is shown while the font is downloading.-->
<ProgressBar
android:id="@+id/emojicompat_progress"
android:id="@+id/emojiProgress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
......@@ -65,12 +64,12 @@
android:indeterminate="false"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/emojicompat_name"
app:layout_constraintTop_toBottomOf="@id/emojicompat_name" />
app:layout_constraintStart_toStartOf="@id/emojiName"
app:layout_constraintTop_toBottomOf="@id/emojiName" />
<!--Click on it and the font will be downloaded!-->
<ImageButton
android:id="@+id/emojicompat_download"
android:id="@+id/emojiDownload"
android:layout_width="42dp"
android:layout_height="42dp"
android:background="?attr/selectableItemBackgroundBorderless"
......@@ -80,13 +79,13 @@
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/emojicompat_caption"
app:layout_constraintStart_toEndOf="@id/emojiCaption"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_file_download_black_24dp" />
<!--You should be able to cancel the download-->
<ImageButton
android:id="@+id/emojicompat_download_cancel"
android:id="@+id/emojiDownloadCancel"
android:layout_width="42dp"
android:layout_height="42dp"
android:background="?attr/selectableItemBackgroundBorderless"
......@@ -96,20 +95,20 @@
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/emojicompat_name"
app:layout_constraintStart_toEndOf="@id/emojiName"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_cancel_24dp" />
<!--You'll probably want to select an emoji font, don't you?-->
<androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/emojicompat_radio"
android:id="@+id/emojiRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
app:buttonTint="@color/compound_button_color"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/emojicompat_name"
app:layout_constraintStart_toEndOf="@id/emojiName"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="12dp"
android:paddingBottom="12dp">
<androidx.emoji.widget.EmojiTextView
android:id="@+id/accountMovedText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="6dp"
android:textSize="?attr/status_text_medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Account has moved" />
<ImageView
android:id="@+id/accountMovedAvatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accountMovedText"
tools:src="@drawable/avatar_default" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/accountMovedDisplayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorPrimary"
android:textSize="?attr/status_text_large"
android:textStyle="normal|bold"
app:layout_constraintBottom_toTopOf="@id/accountMovedUsername"
app:layout_constraintStart_toEndOf="@id/accountMovedAvatar"
app:layout_constraintTop_toTopOf="@id/accountMovedAvatar"
tools:text="Display name" />
<TextView
android:id="@+id/accountMovedUsername"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="?attr/status_text_medium"
app:layout_constraintBottom_toBottomOf="@id/accountMovedAvatar"
app:layout_constraintStart_toEndOf="@id/accountMovedAvatar"
app:layout_constraintTop_toBottomOf="@id/accountMovedDisplayName"
tools:text="\@username" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file