Material Design 3 (#4637)

I tried to find a balance between going fully M3 and keeping some of the
original Tusky feeling.
For example, I removed the "allCaps" setting we had on most buttons,
which is recommended for M3. On the other hand, I made them less rounded
than the M3 default.

<img
src="https://github.com/user-attachments/assets/9d2485e0-7d1d-42ab-8a4e-c30d044aa5dc"
width="320"/>
<img
src="https://github.com/user-attachments/assets/d65d3c91-afe9-424e-92d7-e0f3e401ea4b"
width="320"/>
<img
src="https://github.com/user-attachments/assets/d5634440-c507-4484-a11e-983f47cbeab7"
width="320"/>
This commit is contained in:
Konrad Pozniak 2024-09-10 19:50:09 +02:00 committed by GitHub
commit 50ca44a5f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
94 changed files with 575 additions and 487 deletions

View file

@ -36,6 +36,7 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
import com.keylesspalace.tusky.adapter.AccountSelectionAdapter;
import com.keylesspalace.tusky.components.login.LoginActivity;
@ -260,7 +261,7 @@ public abstract class BaseActivity extends AppCompatActivity {
);
adapter.addAll(accounts);
new AlertDialog.Builder(this)
new MaterialAlertDialogBuilder(this)
.setTitle(dialogTitle)
.setAdapter(adapter, (dialogInterface, index) -> listener.onAccountSelected(accounts.get(index)))
.show();

View file

@ -17,7 +17,6 @@ package com.keylesspalace.tusky
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.util.Log
@ -39,6 +38,9 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.canhub.cropper.CropImage
import com.canhub.cropper.CropImageContract
import com.canhub.cropper.options
import com.google.android.material.R as materialR
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.adapter.AccountFieldEditAdapter
import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
@ -125,7 +127,7 @@ class EditProfileActivity : BaseActivity() {
val plusDrawable = IconicsDrawable(this, GoogleMaterial.Icon.gmd_add).apply {
sizeDp = 12
colorInt = Color.WHITE
colorInt = MaterialColors.getColor(binding.addFieldButton, materialR.attr.colorOnPrimary)
}
binding.addFieldButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
@ -365,7 +367,7 @@ class EditProfileActivity : BaseActivity() {
}
}
private suspend fun launchSaveDialog() = AlertDialog.Builder(this)
private suspend fun launchSaveDialog() = MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.dialog_save_profile_changes_message))
.create()
.await(R.string.action_save, R.string.action_discard)

View file

@ -26,13 +26,13 @@ import android.view.ViewGroup
import android.widget.PopupMenu
import androidx.activity.viewModels
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.databinding.ActivityListsBinding
import com.keylesspalace.tusky.databinding.DialogListBinding
@ -107,10 +107,17 @@ class ListsActivity : BaseActivity() {
}
private fun showlistNameDialog(list: MastoList?) {
var selectedReplyPolicyIndex = 0
val replyPolicies = resources.getStringArray(R.array.list_reply_policies_display)
val binding = DialogListBinding.inflate(layoutInflater).apply {
replyPolicySpinner.setSelection(MastoList.ReplyPolicy.from(list?.repliesPolicy).ordinal)
replyPolicyDropDown.setText(replyPolicies[MastoList.ReplyPolicy.from(list?.repliesPolicy).ordinal])
replyPolicyDropDown.setSimpleItems(replyPolicies)
replyPolicyDropDown.setOnItemClickListener { _, _, position, _ ->
selectedReplyPolicyIndex = position
}
}
val dialog = AlertDialog.Builder(this)
val dialog = MaterialAlertDialogBuilder(this)
.setView(binding.root)
.setPositiveButton(
if (list == null) {
@ -123,7 +130,7 @@ class ListsActivity : BaseActivity() {
binding.nameText.text.toString(),
list?.id,
binding.exclusiveCheckbox.isChecked,
MastoList.ReplyPolicy.entries[binding.replyPolicySpinner.selectedItemPosition].policy
MastoList.ReplyPolicy.entries[selectedReplyPolicyIndex].policy
)
}
.setNegativeButton(android.R.string.cancel, null)
@ -147,7 +154,7 @@ class ListsActivity : BaseActivity() {
}
private fun showListDeleteDialog(list: MastoList) {
AlertDialog.Builder(this)
MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.dialog_delete_list_warning, list.title))
.setPositiveButton(R.string.action_delete) { _, _ ->
viewModel.deleteList(list.id)

View file

@ -60,6 +60,7 @@ import com.bumptech.glide.request.target.FixedSizeDrawable
import com.bumptech.glide.request.transition.Transition
import com.google.android.material.R as materialR
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import com.google.android.material.tabs.TabLayoutMediator
@ -822,7 +823,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
}
private fun buildDeveloperToolsDialog(): AlertDialog {
return AlertDialog.Builder(this)
return MaterialAlertDialogBuilder(this)
.setTitle("Developer Tools")
.setItems(
arrayOf("Create \"Load more\" gap")
@ -1003,7 +1004,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
}
private fun logout() {
AlertDialog.Builder(this)
MaterialAlertDialogBuilder(this)
.setTitle(R.string.action_logout)
.setMessage(getString(R.string.action_logout_confirm, activeAccount.fullName))
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->

View file

@ -31,6 +31,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionManager
import at.connyduck.sparkbutton.helpers.Utils
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.transition.MaterialArcMotion
import com.google.android.material.transition.MaterialContainerTransform
import com.keylesspalace.tusky.adapter.ItemInteractionListener
@ -238,7 +239,7 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener, ListSelec
editText.setText("")
frameLayout.addView(editText)
val dialog = AlertDialog.Builder(this)
val dialog = MaterialAlertDialogBuilder(this)
.setTitle(R.string.add_hashtag_title)
.setView(frameLayout)
.setNegativeButton(android.R.string.cancel, null)

View file

@ -34,7 +34,6 @@ import androidx.activity.viewModels
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.annotation.Px
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.app.ActivityOptionsCompat
import androidx.core.graphics.ColorUtils
@ -53,6 +52,7 @@ import com.google.android.material.R as materialR
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.chip.Chip
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
@ -190,9 +190,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
* Load colors and dimensions from resources
*/
private fun loadResources() {
toolbarColor = MaterialColors.getColor(this, materialR.attr.colorSurface, Color.BLACK)
toolbarColor = MaterialColors.getColor(binding.accountToolbar, materialR.attr.colorSurface)
statusBarColorTransparent = getColor(R.color.transparent_statusbar_background)
statusBarColorOpaque = MaterialColors.getColor(this, materialR.attr.colorPrimaryDark, Color.BLACK)
statusBarColorOpaque = MaterialColors.getColor(binding.accountToolbar, materialR.attr.colorPrimaryDark)
avatarSize = resources.getDimension(R.dimen.account_activity_avatar_size)
titleVisibleHeight = resources.getDimensionPixelSize(R.dimen.account_activity_scroll_title_visible_height)
}
@ -320,28 +320,13 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
setDisplayShowTitleEnabled(false)
}
val appBarElevation = resources.getDimension(R.dimen.actionbar_elevation)
val toolbarBackground = MaterialShapeDrawable.createWithElevationOverlay(
this,
appBarElevation
)
toolbarBackground.fillColor = ColorStateList.valueOf(Color.TRANSPARENT)
binding.accountToolbar.background = toolbarBackground
binding.accountToolbar.setBackgroundColor(Color.TRANSPARENT)
binding.accountToolbar.setNavigationIcon(R.drawable.ic_arrow_back_with_background)
binding.accountToolbar.setOverflowIcon(
AppCompatResources.getDrawable(this, R.drawable.ic_more_with_background)
)
binding.accountToolbar.overflowIcon = AppCompatResources.getDrawable(this, R.drawable.ic_more_with_background)
binding.accountHeaderInfoContainer.background = MaterialShapeDrawable.createWithElevationOverlay(this, appBarElevation)
val avatarBackground = MaterialShapeDrawable.createWithElevationOverlay(
this,
appBarElevation
).apply {
val avatarBackground = MaterialShapeDrawable().apply {
fillColor = ColorStateList.valueOf(toolbarColor)
elevation = appBarElevation
shapeAppearanceModel = ShapeAppearanceModel.builder()
.setAllCornerSizes(resources.getDimension(R.dimen.account_avatar_background_radius))
.build()
@ -382,7 +367,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
toolbarColor
) as Int
toolbarBackground.fillColor = ColorStateList.valueOf(evaluatedToolbarColor)
binding.accountToolbar.setBackgroundColor(evaluatedToolbarColor)
binding.swipeToRefreshLayout.isEnabled = verticalOffset == 0
}
@ -874,7 +859,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
}
private fun showFollowRequestPendingDialog() {
AlertDialog.Builder(this)
MaterialAlertDialogBuilder(this)
.setMessage(R.string.dialog_message_cancel_follow_request)
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState() }
.setNegativeButton(android.R.string.cancel, null)
@ -882,7 +867,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
}
private fun showUnfollowWarningDialog() {
AlertDialog.Builder(this)
MaterialAlertDialogBuilder(this)
.setMessage(R.string.dialog_unfollow_warning)
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState() }
.setNegativeButton(android.R.string.cancel, null)
@ -890,7 +875,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
}
private fun showFollowWarningDialog() {
AlertDialog.Builder(this)
MaterialAlertDialogBuilder(this)
.setMessage(R.string.dialog_follow_warning)
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState() }
.setNegativeButton(android.R.string.cancel, null)
@ -901,7 +886,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
if (blockingDomain) {
viewModel.unblockDomain(instance)
} else {
AlertDialog.Builder(this)
MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.mute_domain_warning, instance))
.setPositiveButton(
getString(R.string.mute_domain_warning_dialog_ok)
@ -913,7 +898,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
private fun toggleBlock() {
if (viewModel.relationshipData.value?.data?.blocking != true) {
AlertDialog.Builder(this)
MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.dialog_block_warning, loadedAccount?.username))
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeBlockState() }
.setNegativeButton(android.R.string.cancel, null)
@ -1115,6 +1100,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
badge.isClickable = false
badge.isFocusable = false
badge.setEnsureMinTouchTargetSize(false)
badge.isCloseIconVisible = false
// reset some chip defaults so it looks better for our badge usecase
badge.iconStartPadding = resources.getDimension(R.dimen.profile_badge_icon_start_padding)

View file

@ -24,12 +24,12 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.ListsActivity
import com.keylesspalace.tusky.R
@ -77,7 +77,7 @@ class ListSelectionFragment : DialogFragment() {
val adapter = Adapter()
binding.listsView.adapter = adapter
val dialogBuilder = AlertDialog.Builder(context)
val dialogBuilder = MaterialAlertDialogBuilder(context)
.setView(binding.root)
.setTitle(R.string.select_list_title)
.setNeutralButton(R.string.select_list_manage) { _, _ ->

View file

@ -19,7 +19,6 @@ import android.annotation.SuppressLint
import android.graphics.drawable.Drawable
import android.os.Build
import android.text.SpannableString
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -106,9 +105,11 @@ class AnnouncementAdapter(
item.reactions.forEachIndexed { i, reaction ->
(
chips.getChildAt(i)?.takeUnless { it.id == R.id.addReactionChip } as Chip?
?: Chip(ContextThemeWrapper(chips.context, com.google.android.material.R.style.Widget_MaterialComponents_Chip_Choice)).apply {
?: Chip(chips.context).apply {
isCheckable = true
checkedIcon = null
isCloseIconVisible = false
setChipBackgroundColorResource(R.color.selectable_chip_background)
chips.addView(this, i)
}
)

View file

@ -16,7 +16,6 @@
package com.keylesspalace.tusky.components.compose
import android.Manifest
import android.app.ProgressDialog
import android.content.ClipData
import android.content.Context
import android.content.Intent
@ -47,7 +46,6 @@ import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.core.content.FileProvider
import androidx.core.content.res.use
import androidx.core.view.ContentInfoCompat
@ -66,6 +64,7 @@ import com.google.android.material.R as materialR
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.BuildConfig
@ -1319,14 +1318,14 @@ class ComposeActivity :
private fun getSaveAsDraftOrDiscardDialog(
contentText: String,
contentWarning: String
): AlertDialog.Builder {
): MaterialAlertDialogBuilder {
val warning = if (viewModel.media.value.isNotEmpty()) {
R.string.compose_save_draft_loses_media
} else {
R.string.compose_save_draft
}
return AlertDialog.Builder(this)
return MaterialAlertDialogBuilder(this)
.setMessage(warning)
.setPositiveButton(R.string.action_save) { _, _ ->
viewModel.stopUploads()
@ -1345,14 +1344,14 @@ class ComposeActivity :
private fun getUpdateDraftOrDiscardDialog(
contentText: String,
contentWarning: String
): AlertDialog.Builder {
): MaterialAlertDialogBuilder {
val warning = if (viewModel.media.value.isNotEmpty()) {
R.string.compose_save_draft_loses_media
} else {
R.string.compose_save_draft
}
return AlertDialog.Builder(this)
return MaterialAlertDialogBuilder(this)
.setMessage(warning)
.setPositiveButton(R.string.action_save) { _, _ ->
viewModel.stopUploads()
@ -1368,8 +1367,8 @@ class ComposeActivity :
* User is editing a post (scheduled, or posted), and can either go back to editing, or
* discard the changes.
*/
private fun getContinueEditingOrDiscardDialog(): AlertDialog.Builder {
return AlertDialog.Builder(this)
private fun getContinueEditingOrDiscardDialog(): MaterialAlertDialogBuilder {
return MaterialAlertDialogBuilder(this)
.setMessage(R.string.compose_unsaved_changes)
.setPositiveButton(R.string.action_continue_edit) { _, _ ->
// Do nothing, dialog will dismiss, user can continue editing
@ -1384,8 +1383,8 @@ class ComposeActivity :
* User is editing an existing draft and making it empty.
* The user can either delete the empty draft or go back to editing.
*/
private fun getDeleteEmptyDraftOrContinueEditing(): AlertDialog.Builder {
return AlertDialog.Builder(this)
private fun getDeleteEmptyDraftOrContinueEditing(): MaterialAlertDialogBuilder {
return MaterialAlertDialogBuilder(this)
.setMessage(R.string.compose_delete_draft)
.setPositiveButton(R.string.action_delete) { _, _ ->
viewModel.deleteDraft()
@ -1404,19 +1403,7 @@ class ComposeActivity :
private fun saveDraftAndFinish(contentText: String, contentWarning: String) {
lifecycleScope.launch {
val dialog = if (viewModel.shouldShowSaveDraftDialog()) {
ProgressDialog.show(
this@ComposeActivity,
null,
getString(R.string.saving_draft),
true,
false
)
} else {
null
}
viewModel.saveDraft(contentText, contentWarning)
dialog?.cancel()
finish()
}
}

View file

@ -331,13 +331,6 @@ class ComposeViewModel @Inject constructor(
mediaUploader.cancelUploadScope(*_media.value.map { it.localId }.toIntArray())
}
fun shouldShowSaveDraftDialog(): Boolean {
// if any of the media files need to be downloaded first it could take a while, so show a loading dialog
return _media.value.any { mediaValue ->
mediaValue.uri.scheme == "https"
}
}
suspend fun saveDraft(content: String, contentWarning: String) {
val mediaUris: MutableList<String> = mutableListOf()
val mediaDescriptions: MutableList<String?> = mutableListOf()

View file

@ -20,8 +20,8 @@ package com.keylesspalace.tusky.components.compose.dialog
import android.content.Context
import android.view.LayoutInflater
import android.view.WindowManager
import android.widget.ArrayAdapter
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.DialogAddPollBinding
import com.keylesspalace.tusky.entity.NewPoll
@ -37,7 +37,7 @@ fun showAddPollDialog(
) {
val binding = DialogAddPollBinding.inflate(LayoutInflater.from(context))
val dialog = AlertDialog.Builder(context)
val dialog = MaterialAlertDialogBuilder(context)
.setIcon(R.drawable.ic_poll_24dp)
.setTitle(R.string.create_poll_title)
.setView(binding.root)
@ -63,9 +63,8 @@ fun showAddPollDialog(
val durationLabels = context.resources.getStringArray(
R.array.poll_duration_names
).filterIndexed { index, _ -> durations[index] in minDuration..maxDuration }
binding.pollDurationSpinner.adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, durationLabels).apply {
setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item)
}
binding.pollDurationDropDown.setSimpleItems(durationLabels.toTypedArray())
durations = durations.filter { it in minDuration..maxDuration }
binding.addChoiceButton.setOnClickListener {
@ -79,23 +78,24 @@ fun showAddPollDialog(
val secondsInADay = 60 * 60 * 24
val desiredDuration = poll?.expiresIn ?: secondsInADay
val pollDurationId = durations.indexOfLast {
var selectedDurationIndex = durations.indexOfLast {
it <= desiredDuration
}
binding.pollDurationSpinner.setSelection(pollDurationId)
binding.pollDurationDropDown.setText(durationLabels[selectedDurationIndex], false)
binding.pollDurationDropDown.setOnItemClickListener { _, _, position, _ ->
selectedDurationIndex = position
}
binding.multipleChoicesCheckBox.isChecked = poll?.multiple ?: false
dialog.setOnShowListener {
val button = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
button.setOnClickListener {
val selectedPollDurationId = binding.pollDurationSpinner.selectedItemPosition
onUpdatePoll(
NewPoll(
options = adapter.pollOptions,
expiresIn = durations[selectedPollDurationId],
expiresIn = durations[selectedDurationIndex],
multiple = binding.multipleChoicesCheckBox.isChecked
)
)

View file

@ -20,7 +20,6 @@ import android.graphics.drawable.Drawable
import android.net.Uri
import android.view.WindowManager
import android.widget.FrameLayout
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
@ -30,6 +29,7 @@ import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.keylesspalace.tusky.databinding.DialogFocusBinding
import com.keylesspalace.tusky.entity.Attachment.Focus
import kotlinx.coroutines.launch
@ -98,7 +98,7 @@ fun <T> T.makeFocusDialog(
dialog.dismiss()
}
val dialog = AlertDialog.Builder(this)
val dialog = MaterialAlertDialogBuilder(this)
.setView(dialogBinding.root)
.setPositiveButton(android.R.string.ok, okListener)
.setNegativeButton(android.R.string.cancel, null)

View file

@ -21,7 +21,6 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.PopupMenu
import androidx.core.view.MenuProvider
import androidx.fragment.app.viewModels
@ -35,6 +34,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import at.connyduck.sparkbutton.helpers.Utils
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.StatusListActivity
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder
@ -370,7 +370,7 @@ class ConversationsFragment :
}
private fun deleteConversation(conversation: ConversationViewData) {
AlertDialog.Builder(requireContext())
MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.dialog_delete_conversation_warning)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok) { _, _ ->

View file

@ -3,18 +3,16 @@ package com.keylesspalace.tusky.components.filters
import android.content.Context
import android.content.DialogInterface.BUTTON_POSITIVE
import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.core.view.size
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.lifecycleScope
import at.connyduck.calladapter.networkresult.fold
import com.google.android.material.chip.Chip
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.materialswitch.MaterialSwitch
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.switchmaterial.SwitchMaterial
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.appstore.EventHub
@ -46,7 +44,7 @@ class EditFilterActivity : BaseActivity() {
private lateinit var filter: Filter
private var originalFilter: Filter? = null
private lateinit var contextSwitches: Map<SwitchMaterial, Filter.Kind>
private lateinit var contextSwitches: Map<MaterialSwitch, Filter.Kind>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -112,25 +110,14 @@ class EditFilterActivity : BaseActivity() {
}
)
}
binding.filterDurationSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
viewModel.setDuration(
if (originalFilter?.expiresAt == null) {
position
} else {
position - 1
}
)
}
override fun onNothingSelected(parent: AdapterView<*>?) {
viewModel.setDuration(0)
}
binding.filterDurationDropDown.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
viewModel.setDuration(
if (originalFilter?.expiresAt == null) {
position
} else {
position - 1
}
)
}
validateSaveButton()
@ -177,10 +164,13 @@ class EditFilterActivity : BaseActivity() {
// Populate the UI from the filter's members
private fun loadFilter() {
viewModel.load(filter)
if (filter.expiresAt != null) {
val durationNames = listOf(getString(R.string.duration_no_change)) + resources.getStringArray(R.array.filter_duration_names)
binding.filterDurationSpinner.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, durationNames)
val durationNames = if (filter.expiresAt != null) {
arrayOf(getString(R.string.duration_no_change)) + resources.getStringArray(R.array.filter_duration_names)
} else {
resources.getStringArray(R.array.filter_duration_names)
}
binding.filterDurationDropDown.setSimpleItems(durationNames)
binding.filterDurationDropDown.setText(durationNames[0], false)
}
private fun updateKeywords(newKeywords: List<FilterKeyword>) {
@ -221,7 +211,7 @@ class EditFilterActivity : BaseActivity() {
private fun showAddKeywordDialog() {
val binding = DialogFilterBinding.inflate(layoutInflater)
binding.phraseWholeWord.isChecked = true
AlertDialog.Builder(this)
MaterialAlertDialogBuilder(this)
.setTitle(R.string.filter_keyword_addition_title)
.setView(binding.root)
.setPositiveButton(android.R.string.ok) { _, _ ->
@ -242,7 +232,7 @@ class EditFilterActivity : BaseActivity() {
binding.phraseEditText.setText(keyword.keyword)
binding.phraseWholeWord.isChecked = keyword.wholeWord
AlertDialog.Builder(this)
MaterialAlertDialogBuilder(this)
.setTitle(R.string.filter_edit_keyword_title)
.setView(binding.root)
.setPositiveButton(R.string.filter_dialog_update_button) { _, _ ->

View file

@ -18,14 +18,14 @@
package com.keylesspalace.tusky.components.filters
import android.app.Activity
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.util.await
internal suspend fun Activity.showDeleteFilterDialog(filterTitle: String) = AlertDialog.Builder(
this
)
.setMessage(getString(R.string.dialog_delete_filter_text, filterTitle))
.setCancelable(true)
.create()
.await(R.string.dialog_delete_filter_positive_action, android.R.string.cancel)
internal suspend fun Activity.showDeleteFilterDialog(filterTitle: String): Int {
return MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.dialog_delete_filter_text, filterTitle))
.setCancelable(true)
.create()
.await(R.string.dialog_delete_filter_positive_action, android.R.string.cancel)
}

View file

@ -7,7 +7,6 @@ import android.os.Bundle
import android.util.Log
import android.widget.AutoCompleteTextView
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import androidx.paging.LoadState
@ -15,6 +14,7 @@ import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator
import at.connyduck.calladapter.networkresult.fold
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.R
@ -195,7 +195,7 @@ class FollowedTagsActivity :
)
)
return AlertDialog.Builder(requireActivity())
return MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.dialog_follow_hashtag_title)
.setView(layout)
.setPositiveButton(android.R.string.ok) { _, _ ->

View file

@ -24,11 +24,11 @@ import android.util.Log
import android.view.Menu
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
import at.connyduck.calladapter.networkresult.fold
import com.bumptech.glide.Glide
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.BuildConfig
import com.keylesspalace.tusky.MainActivity
@ -110,7 +110,7 @@ class LoginActivity : BaseActivity() {
binding.loginButton.setOnClickListener { onLoginClick(true) }
binding.whatsAnInstanceTextView.setOnClickListener {
val dialog = AlertDialog.Builder(this)
val dialog = MaterialAlertDialogBuilder(this)
.setMessage(R.string.dialog_whats_an_instance)
.setPositiveButton(R.string.action_close, null)
.show()

View file

@ -32,15 +32,14 @@ import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.BuildConfig
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ActivityLoginWebviewBinding
import com.keylesspalace.tusky.util.getParcelableExtraCompat
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.util.visible
import dagger.hilt.android.AndroidEntryPoint
@ -196,7 +195,7 @@ class LoginWebViewActivity : BaseActivity() {
viewModel.instanceRules.collect { instanceRules ->
binding.loginRules.visible(instanceRules.isNotEmpty())
binding.loginRules.setOnClickListener {
AlertDialog.Builder(this@LoginWebViewActivity)
MaterialAlertDialogBuilder(this@LoginWebViewActivity)
.setTitle(getString(R.string.instance_rule_title, data.domain))
.setMessage(
instanceRules.joinToString(separator = "\n\n") { "$it" }

View file

@ -27,7 +27,6 @@ import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ListView
import android.widget.PopupWindow
import androidx.appcompat.app.AlertDialog
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.MenuProvider
import androidx.fragment.app.viewModels
@ -43,6 +42,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import at.connyduck.calladapter.networkresult.onFailure
import at.connyduck.sparkbutton.helpers.Utils
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder
@ -416,7 +416,7 @@ class NotificationsFragment :
}
private fun confirmClearNotifications() {
AlertDialog.Builder(requireContext())
MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.notification_clear_text)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> clearNotifications() }
.setNegativeButton(android.R.string.cancel, null)

View file

@ -22,13 +22,13 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.core.view.MenuProvider
import androidx.lifecycle.lifecycleScope
import androidx.paging.LoadState
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.appstore.EventHub
@ -168,7 +168,7 @@ class ScheduledStatusActivity :
}
override fun delete(item: ScheduledStatus) {
AlertDialog.Builder(this)
MaterialAlertDialogBuilder(this)
.setMessage(R.string.delete_scheduled_post_warning)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok) { _, _ ->

View file

@ -27,7 +27,6 @@ import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.PopupMenu
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.getSystemService
@ -39,6 +38,7 @@ import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import at.connyduck.calladapter.networkresult.fold
import at.connyduck.calladapter.networkresult.onFailure
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.ViewMediaActivity
@ -485,7 +485,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
}
private fun onBlock(accountId: String, accountUsername: String) {
AlertDialog.Builder(requireContext())
MaterialAlertDialogBuilder(requireContext())
.setMessage(getString(R.string.dialog_block_warning, accountUsername))
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.blockAccount(accountId) }
.setNegativeButton(android.R.string.cancel, null)
@ -555,7 +555,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
private fun showConfirmDeleteDialog(id: String, position: Int) {
context?.let {
AlertDialog.Builder(it)
MaterialAlertDialogBuilder(it)
.setMessage(R.string.dialog_delete_post_warning)
.setPositiveButton(android.R.string.ok) { _, _ ->
viewModel.deleteStatusAsync(id)
@ -568,7 +568,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
private fun showConfirmEditDialog(id: String, position: Int, status: Status) {
context?.let { context ->
AlertDialog.Builder(context)
MaterialAlertDialogBuilder(context)
.setMessage(R.string.dialog_redraft_post_warning)
.setPositiveButton(android.R.string.ok) { _, _ ->
viewLifecycleOwner.lifecycleScope.launch {

View file

@ -22,10 +22,10 @@ import android.content.Context
import android.os.Build
import android.util.Log
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.preference.PreferenceManager
import at.connyduck.calladapter.networkresult.onFailure
import at.connyduck.calladapter.networkresult.onSuccess
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.login.LoginActivity
@ -73,7 +73,7 @@ fun showMigrationNoticeIfNecessary(
}
private fun showMigrationExplanationDialog(context: Context, accountManager: AccountManager) {
AlertDialog.Builder(context).apply {
MaterialAlertDialogBuilder(context).apply {
if (currentAccountNeedsMigration(accountManager)) {
setMessage(R.string.dialog_push_notification_migration)
setPositiveButton(R.string.title_migration_relogin) { _, _ ->

View file

@ -33,7 +33,7 @@ class ViewThreadActivity : BottomSheetActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
setSupportActionBar(binding.includedToolbar.toolbar)
supportActionBar?.run {
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)

View file

@ -18,10 +18,10 @@ package com.keylesspalace.tusky.db
import android.content.Context
import android.content.DialogInterface
import android.util.Log
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.drafts.DraftsActivity
import com.keylesspalace.tusky.db.dao.DraftDao
@ -61,7 +61,7 @@ class DraftsAlert @Inject constructor(db: AppDatabase) {
draftsNeedUserAlert.collect { count ->
Log.d(TAG, "User id $activeAccountId changed: Notification-worthy draft count $count")
if (count > 0) {
AlertDialog.Builder(context)
MaterialAlertDialogBuilder(context)
.setTitle(R.string.action_post_failed)
.setMessage(
context.resources.getQuantityString(R.plurals.action_post_failed_detail, count)

View file

@ -28,7 +28,6 @@ import android.view.View
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.PopupMenu
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.getSystemService
@ -36,6 +35,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import at.connyduck.calladapter.networkresult.fold
import at.connyduck.calladapter.networkresult.onFailure
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.BottomSheetActivity
@ -383,7 +383,7 @@ abstract class SFragment(@LayoutRes contentLayoutId: Int) : Fragment(contentLayo
}
private fun onBlock(accountId: String, accountUsername: String) {
AlertDialog.Builder(requireContext())
MaterialAlertDialogBuilder(requireContext())
.setMessage(getString(R.string.dialog_block_warning, accountUsername))
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
lifecycleScope.launch {
@ -428,7 +428,7 @@ abstract class SFragment(@LayoutRes contentLayoutId: Int) : Fragment(contentLayo
}
private fun showConfirmDeleteDialog(id: String, position: Int) {
AlertDialog.Builder(requireActivity())
MaterialAlertDialogBuilder(requireActivity())
.setMessage(R.string.dialog_delete_post_warning)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
viewLifecycleOwner.lifecycleScope.launch {
@ -452,7 +452,7 @@ abstract class SFragment(@LayoutRes contentLayoutId: Int) : Fragment(contentLayo
private fun showConfirmEditDialog(id: String, position: Int, status: Status) {
val context = context ?: return
AlertDialog.Builder(context)
MaterialAlertDialogBuilder(context)
.setMessage(R.string.dialog_redraft_post_warning)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
viewLifecycleOwner.lifecycleScope.launch {

View file

@ -12,7 +12,7 @@ import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreference
import androidx.preference.SwitchPreferenceCompat
import com.keylesspalace.tusky.view.SliderPreference
import de.c1710.filemojicompat_ui.views.picker.preference.EmojiPickerPreference
@ -56,9 +56,9 @@ inline fun PreferenceParent.sliderPreference(
}
inline fun PreferenceParent.switchPreference(
builder: SwitchPreference.() -> Unit
): SwitchPreference {
val pref = SwitchPreference(context)
builder: SwitchPreferenceCompat.() -> Unit
): SwitchPreferenceCompat {
val pref = SwitchPreferenceCompat(context)
builder(pref)
addPref(pref)
return pref

View file

@ -8,12 +8,12 @@ import android.view.View
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
import android.widget.ArrayAdapter
import androidx.appcompat.app.AlertDialog
import androidx.core.view.AccessibilityDelegateCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder
import com.keylesspalace.tusky.entity.Status.Companion.MAX_MEDIA_ATTACHMENTS
@ -174,7 +174,7 @@ class ListStatusAccessibilityDelegate(
val status = getStatus(host) as? StatusViewData.Concrete ?: return
val links = getLinks(status).toList()
val textLinks = links.map { item -> item.link }
AlertDialog.Builder(host.context)
MaterialAlertDialogBuilder(host.context)
.setTitle(R.string.title_links_dialog)
.setAdapter(
ArrayAdapter(
@ -191,7 +191,7 @@ class ListStatusAccessibilityDelegate(
val status = getStatus(host) as? StatusViewData.Concrete ?: return
val mentions = status.actionable.mentions
val stringMentions = mentions.map { it.username }
AlertDialog.Builder(host.context)
MaterialAlertDialogBuilder(host.context)
.setTitle(R.string.title_mentions_dialog)
.setAdapter(
ArrayAdapter<CharSequence>(
@ -209,7 +209,7 @@ class ListStatusAccessibilityDelegate(
private fun showHashtagsDialog(host: View) {
val status = getStatus(host) as? StatusViewData.Concrete ?: return
val tags = getHashtags(status).map { it.subSequence(1, it.length) }.toList()
AlertDialog.Builder(host.context)
MaterialAlertDialogBuilder(host.context)
.setTitle(R.string.title_hashtags_dialog)
.setAdapter(
ArrayAdapter(

View file

@ -16,7 +16,7 @@
package com.keylesspalace.tusky.view
import android.content.Context
import android.graphics.Color
import android.content.res.ColorStateList
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.core.content.res.use
@ -32,19 +32,13 @@ class LicenseCard
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
defStyleAttr: Int = R.attr.licenseCardStyle
) : MaterialCardView(context, attrs, defStyleAttr) {
init {
val binding = CardLicenseBinding.inflate(LayoutInflater.from(context), this)
setCardBackgroundColor(
MaterialColors.getColor(
context,
materialR.attr.colorSurface,
Color.BLACK
)
)
setStrokeColor(ColorStateList.valueOf(MaterialColors.getColor(this, materialR.attr.colorOutline)))
val (name, license, link) = context.theme.obtainStyledAttributes(
attrs,

View file

@ -3,7 +3,7 @@
package com.keylesspalace.tusky.view
import android.app.Activity
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.DialogMuteAccountBinding
@ -16,17 +16,26 @@ fun showMuteAccountDialog(
binding.warning.text = activity.getString(R.string.dialog_mute_warning, accountUsername)
binding.checkbox.isChecked = true
AlertDialog.Builder(activity)
val durationLabels = activity.resources.getStringArray(R.array.mute_duration_names)
binding.durationDropDown.setSimpleItems(durationLabels)
var selectedDurationIndex = 0
binding.durationDropDown.setOnItemClickListener { _, _, position, _ ->
selectedDurationIndex = position
}
binding.durationDropDown.setText(durationLabels[selectedDurationIndex], false)
MaterialAlertDialogBuilder(activity)
.setView(binding.root)
.setPositiveButton(android.R.string.ok) { _, _ ->
val durationValues = activity.resources.getIntArray(R.array.mute_duration_values)
// workaround to make indefinite muting work with Mastodon 3.3.0
// https://github.com/tuskyapp/Tusky/issues/2107
val duration = if (binding.duration.selectedItemPosition == 0) {
val duration = if (selectedDurationIndex == 0) {
null
} else {
durationValues[binding.duration.selectedItemPosition]
durationValues[selectedDurationIndex]
}
onOk(binding.checkbox.isChecked, duration)