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

...
 
Commits (29)
......@@ -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) {
......
......@@ -22,8 +22,8 @@ import android.view.LayoutInflater
import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
import com.keylesspalace.tusky.R
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,
......@@ -33,12 +33,12 @@ fun showAddPollDialog(
onUpdatePoll: (NewPoll) -> Unit
) {
val view = LayoutInflater.from(context).inflate(R.layout.dialog_add_poll, null)
val binding = DialogAddPollBinding.inflate(LayoutInflater.from(context))
val dialog = AlertDialog.Builder(context)
.setIcon(R.drawable.ic_poll_24dp)
.setTitle(R.string.create_poll_title)
.setView(view)
.setView(binding.root)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, null)
.create()
......@@ -47,7 +47,7 @@ fun showAddPollDialog(
options = poll?.options?.toMutableList() ?: mutableListOf("", ""),
maxOptionLength = maxOptionLength,
onOptionRemoved = { valid ->
view.addChoiceButton.isEnabled = true
binding.addChoiceButton.isEnabled = true
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = valid
},
onOptionChanged = { valid ->
......@@ -55,9 +55,9 @@ fun showAddPollDialog(
}
)
view.pollChoices.adapter = adapter
binding.pollChoices.adapter = adapter
view.addChoiceButton.setOnClickListener {
binding.addChoiceButton.setOnClickListener {
if (adapter.itemCount < maxOptionCount) {
adapter.addChoice()
}
......@@ -70,14 +70,14 @@ fun showAddPollDialog(
it <= poll?.expiresIn ?: 0
}
view.pollDurationSpinner.setSelection(pollDurationId)
binding.pollDurationSpinner.setSelection(pollDurationId)
view.multipleChoicesCheckBox.isChecked = poll?.multiple ?: false
binding.multipleChoicesCheckBox.isChecked = poll?.multiple ?: false
dialog.setOnShowListener {
val button = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
button.setOnClickListener {
val selectedPollDurationId = view.pollDurationSpinner.selectedItemPosition
val selectedPollDurationId = binding.pollDurationSpinner.selectedItemPosition
val pollDuration = context.resources
.getIntArray(R.array.poll_duration_values)[selectedPollDurationId]
......@@ -85,7 +85,7 @@ fun showAddPollDialog(
onUpdatePoll(NewPoll(
options = adapter.pollOptions,
expiresIn = pollDuration,
multiple = view.multipleChoicesCheckBox.isChecked
multiple = binding.multipleChoicesCheckBox.isChecked
))
dialog.dismiss()
......@@ -96,5 +96,4 @@ fun showAddPollDialog(
// make the dialog focusable so the keyboard does not stay behind it
dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
}
\ 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
......@@ -167,7 +167,7 @@ fun Account.toEntity() =
ConversationAccountEntity(
id,
username,
displayName.orEmpty(),
name,
avatar,
emojis ?: emptyList()
)
......
......@@ -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()
......
......@@ -298,7 +298,7 @@ fun Account.toEntity(accountId: Long, gson: Gson): TimelineAccountEntity {
timelineUserId = accountId,
localUsername = localUsername,
username = username,
displayName = displayName.orEmpty(),
displayName = name,
url = url,
avatar = avatar,
emojis = gson.toJson(emojis),
......
......@@ -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
......@@ -52,7 +52,7 @@ public final class ViewDataUtils {
.setSensitive(visibleStatus.getSensitive())
.setIsShowingSensitiveContent(alwaysShowSensitiveMedia || !visibleStatus.getSensitive())
.setSpoilerText(visibleStatus.getSpoilerText())
.setRebloggedByUsername(status.getReblog() == null ? null : status.getAccount().getDisplayName())
.setRebloggedByUsername(status.getReblog() == null ? null : status.getAccount().getName())
.setUserFullName(visibleStatus.getAccount().getName())
.setVisibility(visibleStatus.getVisibility())
.setSenderId(visibleStatus.getAccount().getId())
......
......@@ -3,14 +3,14 @@ package com.keylesspalace.tusky.view
import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ViewBackgroundMessageBinding
import com.keylesspalace.tusky.util.visible
import kotlinx.android.synthetic.main.view_background_message.view.*
/**
* This view is used for screens with downloadable content which may fail.
......@@ -22,8 +22,9 @@ class BackgroundMessageView @JvmOverloads constructor(
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private val binding = ViewBackgroundMessageBinding.inflate(LayoutInflater.from(context), this)
init {
View.inflate(context, R.layout.view_background_message, this)
gravity = Gravity.CENTER_HORIZONTAL
orientation = VERTICAL
......@@ -36,11 +37,14 @@ class BackgroundMessageView @JvmOverloads constructor(
* Setup image, message and button.
* If [clickListener] is `null` then the button will be hidden.
*/
fun setup(@DrawableRes imageRes: Int, @StringRes messageRes: Int,
clickListener: ((v: View) -> Unit)? = null) {
messageTextView.setText(messageRes)
imageView.setImageResource(imageRes)
button.setOnClickListener(clickListener)
button.visible(clickListener != null)
fun setup(
@DrawableRes imageRes: Int,
@StringRes messageRes: Int,
clickListener: ((v: View) -> Unit)? = null
) {
binding.messageTextView.setText(messageRes)
binding.imageView.setImageResource(imageRes)
binding.button.setOnClickListener(clickListener)
binding.button.visible(clickListener != null)
}
}
\ 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()
......
......@@ -475,6 +475,7 @@ public abstract class StatusViewData {
application = viewData.application;
statusEmojis = viewData.getStatusEmojis();
accountEmojis = viewData.getAccountEmojis();
rebloggedByAccountEmojis = viewData.getRebloggedByAccountEmojis();
card = viewData.getCard();
isCollapsible = viewData.isCollapsible();
isCollapsed = viewData.isCollapsed();
......
......@@ -6,7 +6,9 @@
android:layout_height="match_parent"
tools:context="com.keylesspalace.tusky.AboutActivity">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<FrameLayout
android:layout_width="match_parent"
......
......@@ -71,7 +71,7 @@
app:layout_constraintStart_toEndOf="@id/accountMuteButton"
app:layout_constraintTop_toTopOf="parent"
tools:text="Follow Requested" />
<com.google.android.material.button.MaterialButton
android:id="@+id/accountSubscribeButton"
style="@style/TuskyButton.Outlined"
......@@ -248,20 +248,63 @@
app:layout_constraintTop_toBottomOf="@id/accountFieldList"
tools:visibility="visible" />
<ViewStub
<androidx.constraintlayout.widget.Group
android:id="@+id/accountMovedView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/accountMovedViewLayout"
android:layout="@layout/view_account_moved"
app:layout_constraintTop_toBottomOf="@id/accountRemoveView" />
android:visibility="gone"
app:constraint_referenced_ids="accountMovedText,accountMovedAvatar,accountMovedDisplayName,accountMovedUsername" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrierRemote"
<androidx.emoji.widget.EmojiTextView
android:id="@+id/accountMovedText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="accountMovedView,accountMovedViewLayout" />
android:layout_marginTop="12dp"
android:drawablePadding="6dp"
android:textSize="?attr/status_text_medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accountRemoveView"
tools:text="Account has moved" />
<ImageView
android:id="@+id/accountMovedAvatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accountMovedText"
tools:src="@drawable/avatar_default" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/accountMovedDisplayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorPrimary"
android:textSize="?attr/status_text_large"
android:textStyle="normal|bold"
app:layout_constraintBottom_toTopOf="@id/accountMovedUsername"
app:layout_constraintStart_toEndOf="@id/accountMovedAvatar"
app:layout_constraintTop_toTopOf="@id/accountMovedAvatar"
tools:text="Display name" />
<TextView
android:id="@+id/accountMovedUsername"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="?attr/status_text_medium"
app:layout_constraintBottom_toBottomOf="@id/accountMovedAvatar"
app:layout_constraintStart_toEndOf="@id/accountMovedAvatar"
app:layout_constraintTop_toBottomOf="@id/accountMovedDisplayName"
tools:text="\@username" />
<LinearLayout
android:id="@+id/accountStatuses"
......@@ -272,7 +315,7 @@
android:orientation="vertical"
app:layout_constraintEnd_toStartOf="@id/accountFollowing"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrierRemote">
app:layout_constraintTop_toBottomOf="@id/accountMovedAvatar">
<TextView
android:id="@+id/accountStatusesTextView"
......@@ -303,7 +346,7 @@
android:orientation="vertical"
app:layout_constraintEnd_toStartOf="@id/accountFollowers"
app:layout_constraintStart_toEndOf="@id/accountStatuses"
app:layout_constraintTop_toBottomOf="@id/barrierRemote">
app:layout_constraintTop_toBottomOf="@id/accountMovedAvatar">
<TextView
android:id="@+id/accountFollowingTextView"
......@@ -333,7 +376,7 @@
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/accountFollowing"
app:layout_constraintTop_toBottomOf="@id/barrierRemote">
app:layout_constraintTop_toBottomOf="@id/accountMovedAvatar">
<TextView
android:id="@+id/accountFollowersTextView"
......
......@@ -2,12 +2,13 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_view_thread"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.keylesspalace.tusky.AccountListActivity">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
......
......@@ -5,7 +5,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/toolbar_basic" />
<include
android:id="@+id/includedToolbar"
layout="@layout/toolbar_basic" />
<ProgressBar
android:id="@+id/progressBar"
......
......@@ -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
......@@ -316,7 +316,7 @@
<string name="description_visiblity_public">Öffentlich </string>
<string name="description_visiblity_private">Folgende</string>
<string name="description_visiblity_direct">Direkt </string>
<string name="hint_list_name">Name auflisten</string>
<string name="hint_list_name">Listenname</string>
<string name="download_media">Medien herunterladen</string>
<string name="downloading_media">Medien werden heruntergeladen</string>
<string name="filter_add_description">zu filternde Phrase</string>
......
This diff is collapsed.
This diff is collapsed.
......@@ -439,7 +439,7 @@
<item quantity="other">%d dagar eftir</item>
</plurals>
<string name="account_note_saved">Vistað!</string>
<string name="account_note_hint">Þí eigin einkaathugasemd um þennan aðgang</string>
<string name="account_note_hint">Þín eigin einkaathugasemd um þennan aðgang</string>
<string name="pref_title_hide_top_toolbar">Fela titil á verkfærastikunni efst</string>
<string name="pref_title_confirm_reblogs">Birta staðfestingarglugga áður en endurbirting fer fram</string>
<string name="pref_title_show_cards_in_timelines">Birta forskoðun tengla á tímalínum</string>
......@@ -459,12 +459,44 @@
<string name="dialog_mute_hide_notifications">Fela tilkynningar</string>
<string name="dialog_mute_warning">Þagga niður í @%s\?</string>
<string name="dialog_block_warning">Loka á @%s\?</string>
<string name="action_unmute_conversation">Hætta að þagga niður í samtölum</string>
<string name="action_mute_conversation">Þagga niður í samtölum</string>
<string name="action_unmute_conversation">Hætta að þagga niður í samtali</string>
<string name="action_mute_conversation">Þagga niður í samtali</string>
<string name="action_unmute_domain">Afþagga %s</string>
<string name="action_mute_notifications_desc">Þagga tilkynningar frá %s</string>
<string name="action_unmute_notifications_desc">Afþagga tilkynningar frá %s</string>
<string name="action_unmute_desc">Afþagga %s</string>
<string name="notification_follow_request_format">%s bað um að fylgjast með þér</string>
<string name="title_announcements">Tilkynningar</string>
<string name="new_drafts_warning">Gerð draga í Tusky hefur verið endurhönnuð til að verða fljótlegri, notendavænni og gallalaus.
\n Þú getur áfram nýtt eldri drög í gegnum sérstakan hnapp í glugganum fyrir drög, en sá eiginleiki verður fjarlægður í framtíðaruppfærslu!</string>
<string name="wellbeing_mode_notice">Sumar upplýsingar sem gætu haft áhrif á andlega vellíðan þína verða faldar. Þetta hefur áhrif á:
\n
\n - Eftirlæti/Endurbirtingar/Tilkynningar um fylgjendabeiðnir
\n - Eftirlæti/Talningu á endurbirtingum tísta
\n - Fylgjendur/Tölfræði færslna í notendasniðum
\n
\n Þetta hefur ekki áhrif á ýti-tilkynningar, en þú getur yfirfarið handvirkt kjörstillingar þínar varðandi tilkynningar.</string>
<string name="action_unsubscribe_account">Segja upp áskrift</string>
<string name="action_subscribe_account">Gerast áskrifandi</string>
<string name="pref_title_animate_custom_emojis">Hreyfa sérsniðin tjáningartákn</string>
<string name="drafts_toot_reply_removed">Tístið sem þú gerðir drög að svari við hefur veriið fjarlægt</string>
<string name="draft_deleted">Eyddi drögum</string>
<string name="drafts_failed_loading_reply">Mistókst að hlaða inn svarupplýsingum</string>
<string name="old_drafts">Eldri drög</string>
<string name="drafts_toot_failed_to_send">Mistókst að senda þetta tíst!</string>
<string name="status_media_attachments">Viðhengi</string>
<string name="status_media_audio">Hljóð</string>
<string name="dialog_delete_list_warning">Ertu viss um að þú viljir eyða %s listanum\?</string>
<string name="duration_indefinite">Ótiltekið</string>
<string name="label_duration">Tímalengd</string>
<string name="error_upload_max_media_reached">Þú getur ekki sent inn fleiri en %1$d myndefnisviðhengi.</string>
<string name="wellbeing_hide_stats_profile">Fela magntölfræði notendasniða</string>
<string name="wellbeing_hide_stats_posts">Fela magntölfræði færslna</string>
<string name="limit_notifications">Takmarka tilkynningar á tímalínu</string>
<string name="review_notifications">Yfirfara tilkynningar</string>
<string name="pref_title_wellbeing_mode">Vellíðan</string>
<string name="notification_subscription_description">Tilkynningar þegar einhver sem þú ert áskrifandi að hefur birt nýtt tíst</string>
<string name="notification_subscription_name">Ný tíst</string>
<string name="pref_title_notification_filter_subscriptions">einhver sem ég er áskrifandi að birti nýtt tíst</string>
<string name="notification_subscription_format">%s sendi inn rétt í þessu</string>
</resources>
\ No newline at end of file
......@@ -456,4 +456,6 @@
<item quantity="other">%s人</item>
</plurals>
<string name="warning_scheduling_interval">Mastodonにおける予約までの最小間隔は5分です。</string>
<string name="notification_subscription_format">%sさんがトゥートしました</string>
<string name="title_announcements">お知らせ</string>
</resources>
\ No newline at end of file
This diff is collapsed.
......@@ -481,4 +481,40 @@
<string name="title_announcements">公告</string>
<string name="account_note_saved">已保存</string>
<string name="account_note_hint">此账号的备注</string>
<string name="action_unsubscribe_account">取消关注</string>
<string name="action_subscribe_account">关注</string>
<string name="drafts_toot_reply_removed">该草稿回复的原嘟文已被删除</string>
<string name="draft_deleted">草稿已删除</string>
<string name="drafts_failed_loading_reply">加载回复信息失败</string>
<string name="old_drafts">旧草稿</string>
<string name="new_drafts_warning">Tusky 的草稿功能已被重新设计,现在它更快、更友好,Bug也更少。
\n 旧草稿依然可以通过新草稿页面的按钮查看,但他们将在未来版本中移除!</string>
<string name="drafts_toot_failed_to_send">嘟文发送失败!</string>
<string name="dialog_delete_list_warning">确认删除列表 %s?</string>
<string name="error_upload_max_media_reached">最多只可上传 %1$d 个媒体附件</string>
<string name="wellbeing_hide_stats_profile">隐藏账号的统计信息</string>
<string name="review_notifications">反馈通知</string>
<string name="wellbeing_hide_stats_posts">隐藏嘟文的统计信息</string>
<string name="limit_notifications">限制时间线通知</string>
<string name="wellbeing_mode_notice">一些可能影响您精神状态的信息将被隐藏,这些信息包括:
\n
\n - 收藏、转发、关注通知
\n - 收藏、转发数
\n - 账号的已关注数量、嘟文数量
\n
\n 推送通知不会被影响,但可以在通知设置中手动禁用。</string>
<string name="pref_title_wellbeing_mode">健康模式</string>
<string name="duration_indefinite">永久</string>
<string name="label_duration">持续时间</string>
<plurals name="poll_info_people">
<item quantity="one">%s 人</item>
<item quantity="other"></item>
</plurals>
<string name="status_media_attachments">附件</string>
<string name="status_media_audio">音频</string>
<string name="notification_subscription_description">当有我关注的用户发送了新嘟文时</string>
<string name="notification_subscription_name">新嘟文</string>
<string name="pref_title_animate_custom_emojis">显示动态自定义Emoji</string>
<string name="pref_title_notification_filter_subscriptions">关注的人发送了新嘟文</string>
<string name="notification_subscription_format">%s 发送了新嘟文</string>
</resources>
\ No newline at end of file
Tusky v6.0
- Timeline filters have moved to Account Preferences and will sync with the server
- You can now have a custom hashtag as tab in the main interface
- Lists can now be edited
- Security: removed support for TLS 1.0 and TLS 1.1, and added support for TLS 1.3 on Android 6+
- The compose view will now suggest custom emojis when starting to type
- New theme setting "follow system theme
https://github.com/tuskyapp/Tusky/releases
Tusky v7.0
- Support for displaying polls, voting and poll notifications
- New buttons to filter the notification tab and to delete all notifications
- delete & redraft your own toots
- new indicator that shows if an account is a bot on the profile image (can be turned off in the preferences)
- New translations: Norwegian Bokmål and Slovenian.
Tusky v9.0
- Podes crear Enquisas desde Tusky
- Busca mellorada
- Nova opción nas Preferencias da Conta para despregar avisos de contido
- Os avatares na caixa de navegación agora son cadrados con bordo redondeado
- Xa podes denunciar usuarias incluso se nunca publicaron
- Tusky rexeitará conectar sobre conexións sen cifrar en Android 6+
- Múltiples melloras e corrección de fallos
Tusky v9.1
Esta versión asegura a compatibilidade con Mastodon 3 e mellora o rendemento e estabilidade.
Tusky v10.0
- You can now bookmark statuses & list your bookmarks in Tusky.
- You can now schedule toots with Tusky. Note that the time you select has to be at least 5 minutes in the future.
- You can now add lists to the main screen.
- You can now post audio attachments with Tusky.
And a lot of other small improvements and bug fixes!
Tusky v11.
- Notifications about new follow requests when your account is locked
- New features that can be toggled on the Preferences screen:
- disable swiping between tabs
- show a confirmation dialog before boosting a toot
- show link previews in timelines
- Conversations can now be muted
- https://github.com/tuskyapp/Tusky/releases
Tusky v12.0
- Melloras na interface - podes mover lapela á zona inferior
- Ao acalar unha usuaria, agora tamén podes decidir se acalas as súas notificacións
- Podes seguir cantos cancelos queiras nunha única lapela de cancelos
- Melloras no xeito en que se mostran as descricións do multimedia, útil para descricións moi longas
...
Rexistro completo dos cambios en https://github.com/tuskyapp/Tusky/releases
Tusky v13.0
- soporte para notas do perfil (Mastodon >3.2.0)
- soporte para anuncios da admin (Mastodon >3.1.0)
- móstrase o avatar da túa conta seleccionada na barra de ferramentas principal
- premendo no nome público nunha cronoloxía abre a páxina de perfil desa usuaria
- corrección de múltiples erros e pequenas melloras
- melloras nas traducións
Tusky v14.0
- Recibe notificacións cando alguén que segues publica - preme na icona da campá no seu perfil! (Mastodon >3.3.0)
- Os borradores en Tusky foron redeseñados para ser máis rápidos, amigables e con menos fallos.
- Novo modo benestar que che permite limitar certas características engadidas a Tusky.
- Tusky xa pode animar os emojis personalizados.
Rexistro completo dos cambios: https://github.com/tuskyapp/Tusky/releases
Tusky é un cliente lixeiro para Mastodon, o servidor para redes sociais libres e de código aberto.
• Material Design
• Maioría das APIs de Mastodon implementadas
• Soporte multi-conta.
• Decorado escuro e claro coa posibilidade de cambio automático según a hora do día
• Borradores - compoñer toots e gardalos para máis tarde
• Escoller entre varios estilos de emoji
• Optimizado para tódolos tamaños de pantalla
• Completamente de código aberto - sen dependencias non-libres como os servizos de Google
Coñece máis acerca de Mastodon, visita visit https://joinmastodon.org/
Cliente multi conta para a rede social Mastodon
Tusky útg. 6
- Tímalínusíur hafa verið færðar í kjörstillingar notandaaðgangs og munu samstillast við vefþjón
- YNú geturðu haft sérsniðið myllumerki sem flipa í aðalviðmóti
- Nú geturðu haft sérsniðið myllumerki sem flipa í aðalviðmóti
- Hægt er að breyta listum
- Öryggi: fjarlægður stuðningur við TLS 1.0 og TLS 1.1, bætt við stuðningi við TLS 1.3 á Android 6+
- Semja-sýnin stingur núna upp á sérsniðnum tjáningartáknum þegar byrjað er að skrifa
......
Tusky útg. 11.0
- Tilkynningar um nýjar fylgjendabeiðnir þegar aðgangur þinn er í lás
- Nýir eiginleikar sem hægt er að víxla af/á í kjörstillingum:
- strokur milli flipa óvirkar
- staðfesting áður en tíst er endurbirt
- birta forskoðun tengla á tímalínum
- Hægt er að þagga niður í samtölum
- Niðurstöður kannana reiknast núna út frá fjölda kjósenda en ekki atkvæðum
- Margar villur lagaðar, flestar varðandi samningu tísta
- Bættar þýðingar
Tusky útg. 14.0
- Fáðu tilkynningu þegar notandi sem þú fylgir birtir færslu - smelltu á bjöllutáknið í sniðinu hans! (Mastodon 3.3.0 eiginleiki)
- Gerð draga í Tusky hefur verið endurhönnuð til að verða fljótlegri, notendavænni og gallalaus.
- Bætt hefur verið við sérstökum vellíðunarham til að takmarka ákveðna eiginleika Tusky.
- Tusky getur núna hreyft sérsniðin tjáningartákn.
Full breytingaskrá: https://github.com/tuskyapp/Tusky/releases
Tusky v6.0
- Фільтри стрічки в Налаштуваннях облікового запису та їхня синхронізація з сервером
- Можна мати власний хештег вкладкою основного інтерфейсу
- Списки можна редагувати
- Безпека: вилучено підтримку TLS 1.0 та TLS 1.1 та додано підтримку TLS 1.3 на Android 6+
- Пропонування власних смайлів, під час введення
- «Слідувати системній темі»
- Поліпшено доступність стрічки
- Нехтування невідомими сповіщеннями без збоїв
- Можна змінювати мову
- Нові переклади: чеська та есперанто
- Інше
Tusky v7.0
- Підтримка показу опитувань, голосування та повідомлень про опитування
- Нові кнопки фільтрування вкладки сповіщень та видалення всіх сповіщень
- видалити та переробити власні дмухи
- новий індикатор, який показує, чи є обліковий запис ботом на зображенні профілю (можна вимкнути в налаштуваннях)
- Нові переклади: норвезька букмол та словенська.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.