Utilise dialog dsl

This commit is contained in:
andrew 2023-05-30 16:49:07 +09:30
parent 1b88d4f950
commit 4ee68cbbb1
14 changed files with 375 additions and 334 deletions

View File

@ -76,6 +76,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import kotlin.Unit;
import network.loki.messenger.R; import network.loki.messenger.R;
/** /**
@ -318,9 +319,9 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
@SuppressWarnings("CodeBlock2Expr") @SuppressWarnings("CodeBlock2Expr")
@SuppressLint({"InlinedApi", "StaticFieldLeak"}) @SuppressLint({"InlinedApi", "StaticFieldLeak"})
private void handleSaveMedia(@NonNull Collection<MediaDatabase.MediaRecord> mediaRecords) { private void handleSaveMedia(@NonNull Collection<MediaDatabase.MediaRecord> mediaRecords) {
final Context context = getContext(); final Context context = requireContext();
SaveAttachmentTask.showWarningDialog(context, (dialogInterface, which) -> { SaveAttachmentTask.showWarningDialog(context, mediaRecords.size(), () -> {
Permissions.with(this) Permissions.with(this)
.request(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) .request(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
.maxSdkVersion(Build.VERSION_CODES.P) .maxSdkVersion(Build.VERSION_CODES.P)
@ -362,7 +363,8 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
}.execute(); }.execute();
}) })
.execute(); .execute();
}, mediaRecords.size()); return Unit.INSTANCE;
});
} }
private void sendMediaSavedNotificationIfNeeded() { private void sendMediaSavedNotificationIfNeeded() {

View File

@ -85,6 +85,7 @@ import java.io.IOException;
import java.util.Locale; import java.util.Locale;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import kotlin.Unit;
import network.loki.messenger.R; import network.loki.messenger.R;
/** /**
@ -416,7 +417,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
MediaItem mediaItem = getCurrentMediaItem(); MediaItem mediaItem = getCurrentMediaItem();
if (mediaItem == null) return; if (mediaItem == null) return;
SaveAttachmentTask.showWarningDialog(this, (dialogInterface, i) -> { SaveAttachmentTask.showWarningDialog(this, () -> {
Permissions.with(this) Permissions.with(this)
.request(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) .request(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
.maxSdkVersion(Build.VERSION_CODES.P) .maxSdkVersion(Build.VERSION_CODES.P)
@ -433,6 +434,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
} }
}) })
.execute(); .execute();
return Unit.INSTANCE;
}); });
} }

View File

@ -8,70 +8,128 @@ import android.widget.Button
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.LinearLayout.VERTICAL import android.widget.LinearLayout.VERTICAL
import android.widget.TextView import android.widget.TextView
import androidx.annotation.AttrRes
import androidx.annotation.LayoutRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.annotation.StyleRes import androidx.annotation.StyleRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.setMargins import androidx.core.view.setMargins
import androidx.core.view.setPadding
import androidx.core.view.updateMargins import androidx.core.view.updateMargins
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.util.toPx import org.thoughtcrime.securesms.util.toPx
import java.lang.ref.Reference
@DslMarker
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class DialogDsl
@DialogDsl
class SessionDialogBuilder(val context: Context) { class SessionDialogBuilder(val context: Context) {
private val dialog: AlertDialog = AlertDialog.Builder(context).create() private val dialogBuilder: AlertDialog.Builder = AlertDialog.Builder(context)
private var dialog: AlertDialog? = null
private fun dismiss() = dialog?.dismiss()
private val topView = LinearLayout(context).apply { orientation = VERTICAL }
.also(dialogBuilder::setCustomTitle)
private val root = LinearLayout(context).apply { orientation = VERTICAL } private val root = LinearLayout(context).apply { orientation = VERTICAL }
.also(dialog::setView) .also(dialogBuilder::setView)
fun title(@StringRes id: Int) { fun title(@StringRes id: Int) = title(context.getString(id))
TextView(context, null, 0, R.style.TextAppearance_AppCompat_Title)
.apply { textAlignment = View.TEXT_ALIGNMENT_CENTER } fun title(text: CharSequence?) = title(text?.toString())
.apply { setText(id) } fun title(text: String?) {
.apply { layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) text(text, R.style.TextAppearance_AppCompat_Title) { setPadding(toPx(20, resources)) }
.apply { setMargins(toPx(20, resources)) }
}.let(root::addView)
} }
fun text(@StringRes id: Int, style: Int = 0) { fun text(@StringRes id: Int, style: Int = 0) = text(context.getString(id), style)
TextView(context, null, 0, style) fun text(text: CharSequence?) = text(text?.toString())
.apply { textAlignment = View.TEXT_ALIGNMENT_CENTER } fun text(text: String?, @StyleRes style: Int = 0) {
.apply { setText(id) } text(text, style) {
.apply { layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
.apply { toPx(40, resources).let { updateMargins(it, 0, it, 0) } } .apply { toPx(40, resources).let { updateMargins(it, 0, it, 0) } }
}.let(root::addView) }
} }
fun buttons(build: ButtonsBuilder.() -> Unit) { private fun text(text: String?, @StyleRes style: Int, modify: TextView.() -> Unit) {
ButtonsBuilder(context, dialog).build(build).let(root::addView) text ?: return
TextView(context, null, 0, style)
.apply {
setText(text)
textAlignment = View.TEXT_ALIGNMENT_CENTER
modify()
}.let(topView::addView)
} }
fun show(): AlertDialog = dialog.apply { show() } fun view(view: View) {
dialogBuilder.setView(view)
}
fun view(@LayoutRes layout: Int) {
dialogBuilder.setView(layout)
}
fun setIconAttribute(@AttrRes icon: Int) {
dialogBuilder.setIconAttribute(icon)
}
fun singleChoiceItems(
options: Array<String>,
currentSelected: Int,
onSelect: (Int) -> Unit
) {
dialogBuilder.setSingleChoiceItems(
options,
currentSelected
) { dialog, it -> onSelect(it); dialog.dismiss() }
}
fun buttons(build: (@DialogDsl ButtonsBuilder).() -> Unit) {
ButtonsBuilder(context, ::dismiss).build(build).let(root::addView)
}
fun show(): AlertDialog = dialogBuilder.show()
} }
class ButtonsBuilder(val context: Context, val dialog: AlertDialog) { @DialogDsl
class ButtonsBuilder(val context: Context, val dismiss: () -> Unit) {
val root = LinearLayout(context) val root = LinearLayout(context)
fun destructiveButton(@StringRes text: Int, @StringRes contentDescription: Int, listener: () -> Unit = {}) { fun destructiveButton(
button(text, contentDescription, R.style.Widget_Session_Button_Dialog_DestructiveText, listener) @StringRes text: Int,
@StringRes contentDescription: Int,
listener: () -> Unit = {}
) {
button(
text,
contentDescription,
R.style.Widget_Session_Button_Dialog_DestructiveText,
listener
)
} }
fun cancelButton() = button(android.R.string.cancel) fun cancelButton(listener: (() -> Unit) = {}) = button(android.R.string.cancel, R.string.AccessibilityId_cancel_button, listener = listener)
fun button( fun button(
@StringRes text: Int, @StringRes text: Int,
@StringRes contentDescriptionRes: Int = 0, @StringRes contentDescriptionRes: Int = 0,
@StyleRes style: Int = R.style.Widget_Session_Button_Dialog_UnimportantText, @StyleRes style: Int = R.style.Widget_Session_Button_Dialog_UnimportantText,
listener: (() -> Unit) = {}) { listener: (() -> Unit) = {}
) {
Button(context, null, 0, style) Button(context, null, 0, style)
.apply { setText(text) }
.apply { setOnClickListener {
listener.invoke()
dialog.dismiss()
contentDescription = resources.getString(contentDescriptionRes)
} }
.apply { .apply {
setText(text)
contentDescription = resources.getString(contentDescriptionRes)
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, 1f) layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, 1f)
.apply { setMargins(toPx(20, resources)) } .apply { setMargins(toPx(20, resources)) }
setOnClickListener {
listener.invoke()
dismiss()
}
} }
.let(root::addView) .let(root::addView)
} }

View File

@ -1461,23 +1461,24 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
private fun showGIFPicker() { private fun showGIFPicker() {
val hasSeenGIFMetaDataWarning: Boolean = textSecurePreferences.hasSeenGIFMetaDataWarning() val hasSeenGIFMetaDataWarning: Boolean = textSecurePreferences.hasSeenGIFMetaDataWarning()
if (!hasSeenGIFMetaDataWarning) { if (!hasSeenGIFMetaDataWarning) {
val builder = AlertDialog.Builder(this) sessionDialog {
builder.setTitle(R.string.giphy_permission_title) title(R.string.giphy_permission_title)
builder.setMessage(R.string.giphy_permission_message) text(R.string.giphy_permission_message)
builder.setPositiveButton(R.string.continue_2) { dialog: DialogInterface, _: Int -> buttons {
button(R.string.continue_2) {
textSecurePreferences.setHasSeenGIFMetaDataWarning() textSecurePreferences.setHasSeenGIFMetaDataWarning()
AttachmentManager.selectGif(this, PICK_GIF) selectGif()
dialog.dismiss() }
cancelButton()
} }
builder.setNegativeButton(R.string.cancel) { dialog: DialogInterface, _: Int ->
dialog.dismiss()
} }
builder.create().show()
} else { } else {
AttachmentManager.selectGif(this, PICK_GIF) selectGif()
} }
} }
private fun selectGif() = AttachmentManager.selectGif(this, PICK_GIF)
private fun showDocumentPicker() { private fun showDocumentPicker() {
AttachmentManager.selectDocument(this, PICK_DOCUMENT) AttachmentManager.selectDocument(this, PICK_DOCUMENT)
} }
@ -1624,35 +1625,25 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val allHasHash = messages.all { lokiMessageDb.getMessageServerHash(it.id) != null } val allHasHash = messages.all { lokiMessageDb.getMessageServerHash(it.id) != null }
if (recipient.isOpenGroupRecipient) { if (recipient.isOpenGroupRecipient) {
val messageCount = 1 val messageCount = 1
val builder = AlertDialog.Builder(this)
builder.setTitle(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount)) sessionDialog {
builder.setMessage(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount)) title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount))
builder.setCancelable(true) text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount))
builder.setPositiveButton(R.string.delete) { _, _ -> buttons {
for (message in messages) { button(R.string.delete) { messages.forEach(viewModel::deleteForEveryone); endActionMode() }
viewModel.deleteForEveryone(message) cancelButton { endActionMode() }
} }
endActionMode()
} }
builder.setNegativeButton(android.R.string.cancel) { dialog, _ ->
dialog.dismiss()
endActionMode()
}
builder.show()
} else if (allSentByCurrentUser && allHasHash) { } else if (allSentByCurrentUser && allHasHash) {
val bottomSheet = DeleteOptionsBottomSheet() val bottomSheet = DeleteOptionsBottomSheet()
bottomSheet.recipient = recipient bottomSheet.recipient = recipient
bottomSheet.onDeleteForMeTapped = { bottomSheet.onDeleteForMeTapped = {
for (message in messages) { messages.forEach(viewModel::deleteLocally)
viewModel.deleteLocally(message)
}
bottomSheet.dismiss() bottomSheet.dismiss()
endActionMode() endActionMode()
} }
bottomSheet.onDeleteForEveryoneTapped = { bottomSheet.onDeleteForEveryoneTapped = {
for (message in messages) { messages.forEach(viewModel::deleteForEveryone)
viewModel.deleteForEveryone(message)
}
bottomSheet.dismiss() bottomSheet.dismiss()
endActionMode() endActionMode()
} }
@ -1663,54 +1654,38 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
bottomSheet.show(supportFragmentManager, bottomSheet.tag) bottomSheet.show(supportFragmentManager, bottomSheet.tag)
} else { } else {
val messageCount = 1 val messageCount = 1
val builder = AlertDialog.Builder(this)
builder.setTitle(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount)) sessionDialog {
builder.setMessage(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount)) title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount))
builder.setCancelable(true) text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount))
builder.setPositiveButton(R.string.delete) { _, _ -> buttons {
for (message in messages) { button(R.string.delete) { messages.forEach(viewModel::deleteLocally); endActionMode() }
viewModel.deleteLocally(message) cancelButton(::endActionMode)
} }
endActionMode()
} }
builder.setNegativeButton(android.R.string.cancel) { dialog, _ ->
dialog.dismiss()
endActionMode()
}
builder.show()
} }
} }
override fun banUser(messages: Set<MessageRecord>) { override fun banUser(messages: Set<MessageRecord>) {
val builder = AlertDialog.Builder(this) sessionDialog {
builder.setTitle(R.string.ConversationFragment_ban_selected_user) title(R.string.ConversationFragment_ban_selected_user)
builder.setMessage("This will ban the selected user from this room. It won't ban them from other rooms.") text("This will ban the selected user from this room. It won't ban them from other rooms.")
builder.setCancelable(true) buttons {
builder.setPositiveButton(R.string.ban) { _, _ -> button(R.string.ban) { viewModel.banUser(messages.first().individualRecipient); endActionMode() }
viewModel.banUser(messages.first().individualRecipient) cancelButton(::endActionMode)
endActionMode()
} }
builder.setNegativeButton(android.R.string.cancel) { dialog, _ ->
dialog.dismiss()
endActionMode()
} }
builder.show()
} }
override fun banAndDeleteAll(messages: Set<MessageRecord>) { override fun banAndDeleteAll(messages: Set<MessageRecord>) {
val builder = AlertDialog.Builder(this) sessionDialog {
builder.setTitle(R.string.ConversationFragment_ban_selected_user) title(R.string.ConversationFragment_ban_selected_user)
builder.setMessage("This will ban the selected user from this room and delete all messages sent by them. It won't ban them from other rooms or delete the messages they sent there.") text("This will ban the selected user from this room and delete all messages sent by them. It won't ban them from other rooms or delete the messages they sent there.")
builder.setCancelable(true) buttons {
builder.setPositiveButton(R.string.ban) { _, _ -> button(R.string.ban) { viewModel.banAndDeleteAll(messages.first().individualRecipient); endActionMode() }
viewModel.banAndDeleteAll(messages.first().individualRecipient) cancelButton(::endActionMode)
endActionMode()
} }
builder.setNegativeButton(android.R.string.cancel) { dialog, _ ->
dialog.dismiss()
endActionMode()
} }
builder.show()
} }
override fun copyMessages(messages: Set<MessageRecord>) { override fun copyMessages(messages: Set<MessageRecord>) {
@ -1774,7 +1749,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun saveAttachment(messages: Set<MessageRecord>) { override fun saveAttachment(messages: Set<MessageRecord>) {
val message = messages.first() as MmsMessageRecord val message = messages.first() as MmsMessageRecord
SaveAttachmentTask.showWarningDialog(this, { _, _ -> SaveAttachmentTask.showWarningDialog(this) {
Permissions.with(this) Permissions.with(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE) .request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.maxSdkVersion(Build.VERSION_CODES.P) .maxSdkVersion(Build.VERSION_CODES.P)
@ -1802,7 +1777,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
Toast.LENGTH_LONG).show() Toast.LENGTH_LONG).show()
} }
.execute() .execute()
}) }
} }
override fun reply(messages: Set<MessageRecord>) { override fun reply(messages: Set<MessageRecord>) {

View File

@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
import org.thoughtcrime.securesms.sessionDialog
class ConversationAdapter( class ConversationAdapter(
context: Context, context: Context,
@ -146,17 +147,17 @@ class ConversationAdapter(
viewHolder.view.bind(message, messageBefore) viewHolder.view.bind(message, messageBefore)
if (message.isCallLog && message.isFirstMissedCall) { if (message.isCallLog && message.isFirstMissedCall) {
viewHolder.view.setOnClickListener { viewHolder.view.setOnClickListener {
AlertDialog.Builder(context) context.sessionDialog {
.setTitle(R.string.CallNotificationBuilder_first_call_title) title(R.string.CallNotificationBuilder_first_call_title)
.setMessage(R.string.CallNotificationBuilder_first_call_message) text(R.string.CallNotificationBuilder_first_call_message)
.setPositiveButton(R.string.activity_settings_title) { _, _ -> buttons {
val intent = Intent(context, PrivacySettingsActivity::class.java) button(R.string.activity_settings_title) {
context.startActivity(intent) Intent(context, PrivacySettingsActivity::class.java)
.let(context::startActivity)
}
cancelButton()
} }
.setNeutralButton(R.string.cancel) { d, _ ->
d.dismiss()
} }
.show()
} }
} else { } else {
viewHolder.view.setOnClickListener(null) viewHolder.view.setOnClickListener(null)

View File

@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.groups.EditClosedGroupActivity
import org.thoughtcrime.securesms.groups.EditClosedGroupActivity.Companion.groupIDKey import org.thoughtcrime.securesms.groups.EditClosedGroupActivity.Companion.groupIDKey
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
import org.thoughtcrime.securesms.service.WebRtcCallService import org.thoughtcrime.securesms.service.WebRtcCallService
import org.thoughtcrime.securesms.sessionDialog
import org.thoughtcrime.securesms.util.BitmapUtil import org.thoughtcrime.securesms.util.BitmapUtil
import java.io.IOException import java.io.IOException
@ -186,29 +187,25 @@ object ConversationMenuHelper {
private fun call(context: Context, thread: Recipient) { private fun call(context: Context, thread: Recipient) {
if (!TextSecurePreferences.isCallNotificationsEnabled(context)) { if (!TextSecurePreferences.isCallNotificationsEnabled(context)) {
val dialog = AlertDialog.Builder(context) context.sessionDialog {
.setTitle(R.string.ConversationActivity_call_title) title(R.string.ConversationActivity_call_title)
.setMessage(R.string.ConversationActivity_call_prompt) text(R.string.ConversationActivity_call_prompt)
.setPositiveButton(R.string.activity_settings_title) { _, _ -> buttons {
val intent = Intent(context, PrivacySettingsActivity::class.java) button(R.string.activity_settings_title, R.string.AccessibilityId_settings) {
context.startActivity(intent) Intent(context, PrivacySettingsActivity::class.java).let(context::startActivity)
}
cancelButton()
}
} }
.setNeutralButton(R.string.cancel) { d, _ ->
d.dismiss()
}.create()
dialog.getButton(DialogInterface.BUTTON_POSITIVE)?.contentDescription = context.getString(R.string.AccessibilityId_settings)
dialog.getButton(DialogInterface.BUTTON_NEGATIVE)?.contentDescription = context.getString(R.string.AccessibilityId_cancel_button)
dialog.show()
return return
} }
val service = WebRtcCallService.createCall(context, thread) WebRtcCallService.createCall(context, thread)
context.startService(service) .let(context::startService)
val activity = Intent(context, WebRtcCallActivity::class.java).apply { Intent(context, WebRtcCallActivity::class.java)
flags = Intent.FLAG_ACTIVITY_NEW_TASK .apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
} .let(context::startActivity)
context.startActivity(activity)
} }
@ -295,9 +292,7 @@ object ConversationMenuHelper {
private fun leaveClosedGroup(context: Context, thread: Recipient) { private fun leaveClosedGroup(context: Context, thread: Recipient) {
if (!thread.isClosedGroupRecipient) { return } if (!thread.isClosedGroupRecipient) { return }
val builder = AlertDialog.Builder(context)
builder.setTitle(context.resources.getString(R.string.ConversationActivity_leave_group))
builder.setCancelable(true)
val group = DatabaseComponent.get(context).groupDatabase().getGroup(thread.address.toGroupString()).orNull() val group = DatabaseComponent.get(context).groupDatabase().getGroup(thread.address.toGroupString()).orNull()
val admins = group.admins val admins = group.admins
val sessionID = TextSecurePreferences.getLocalNumber(context) val sessionID = TextSecurePreferences.getLocalNumber(context)
@ -307,29 +302,27 @@ object ConversationMenuHelper {
} else { } else {
context.resources.getString(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group) context.resources.getString(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group)
} }
builder.setMessage(message)
builder.setPositiveButton(R.string.yes) { _, _ -> fun onLeaveFailed() = Toast.makeText(context, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show()
var groupPublicKey: String?
var isClosedGroup: Boolean context.sessionDialog {
title(R.string.ConversationActivity_leave_group)
text(message)
buttons {
button(R.string.yes) {
try { try {
groupPublicKey = doubleDecodeGroupID(thread.address.toString()).toHexString() val groupPublicKey = doubleDecodeGroupID(thread.address.toString()).toHexString()
isClosedGroup = DatabaseComponent.get(context).lokiAPIDatabase().isClosedGroup(groupPublicKey) val isClosedGroup = DatabaseComponent.get(context).lokiAPIDatabase().isClosedGroup(groupPublicKey)
} catch (e: IOException) {
groupPublicKey = null if (isClosedGroup) MessageSender.leave(groupPublicKey, true)
isClosedGroup = false else onLeaveFailed()
}
try {
if (isClosedGroup) {
MessageSender.leave(groupPublicKey!!, true)
} else {
Toast.makeText(context, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show()
}
} catch (e: Exception) { } catch (e: Exception) {
Toast.makeText(context, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show() onLeaveFailed()
}
}
button(R.string.no)
} }
} }
builder.setNegativeButton(R.string.no, null)
builder.show()
} }
private fun inviteContacts(context: Context, thread: Recipient) { private fun inviteContacts(context: Context, thread: Recipient) {

View File

@ -1,21 +1,18 @@
package org.thoughtcrime.securesms.conversation.v2.utilities package org.thoughtcrime.securesms.conversation.v2.utilities
import android.content.Context import android.content.Context
import androidx.appcompat.app.AlertDialog
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.sessionDialog
object NotificationUtils { object NotificationUtils {
fun showNotifyDialog(context: Context, thread: Recipient, notifyTypeHandler: (Int)->Unit) { fun showNotifyDialog(context: Context, thread: Recipient, notifyTypeHandler: (Int)->Unit) {
val notifyTypes = context.resources.getStringArray(R.array.notify_types) context.sessionDialog {
val currentSelected = thread.notifyType title(R.string.RecipientPreferenceActivity_notification_settings)
singleChoiceItems(
AlertDialog.Builder(context) context.resources.getStringArray(R.array.notify_types),
.setSingleChoiceItems(notifyTypes,currentSelected) { d, newSelection -> thread.notifyType
notifyTypeHandler(newSelection) ) { notifyTypeHandler(it) }
d.dismiss()
} }
.setTitle(R.string.RecipientPreferenceActivity_notification_settings)
.show()
} }
} }

View File

@ -63,6 +63,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.onboarding.SeedActivity import org.thoughtcrime.securesms.onboarding.SeedActivity
import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate
import org.thoughtcrime.securesms.preferences.SettingsActivity import org.thoughtcrime.securesms.preferences.SettingsActivity
import org.thoughtcrime.securesms.sessionDialog
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.IP2Country import org.thoughtcrime.securesms.util.IP2Country
@ -488,46 +489,43 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
} }
private fun blockConversation(thread: ThreadRecord) { private fun blockConversation(thread: ThreadRecord) {
AlertDialog.Builder(this) sessionDialog {
.setTitle(R.string.RecipientPreferenceActivity_block_this_contact_question) title(R.string.RecipientPreferenceActivity_block_this_contact_question)
.setMessage(R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact) text(R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact)
.setNegativeButton(android.R.string.cancel, null) buttons {
.setPositiveButton(R.string.RecipientPreferenceActivity_block) { dialog, _ -> button(R.string.RecipientPreferenceActivity_block) {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
recipientDatabase.setBlocked(thread.recipient, true) recipientDatabase.setBlocked(thread.recipient, true)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
binding.recyclerView.adapter!!.notifyDataSetChanged() binding.recyclerView.adapter?.notifyDataSetChanged()
dialog.dismiss() }
}
}
cancelButton()
} }
} }
}.show()
} }
private fun unblockConversation(thread: ThreadRecord) { private fun unblockConversation(thread: ThreadRecord) {
AlertDialog.Builder(this) sessionDialog {
.setTitle(R.string.RecipientPreferenceActivity_unblock_this_contact_question) title(R.string.RecipientPreferenceActivity_unblock_this_contact_question)
.setMessage(R.string.RecipientPreferenceActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact) text(R.string.RecipientPreferenceActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact)
.setNegativeButton(android.R.string.cancel, null) buttons {
.setPositiveButton(R.string.RecipientPreferenceActivity_unblock) { dialog, _ -> button(R.string.RecipientPreferenceActivity_unblock) {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
recipientDatabase.setBlocked(thread.recipient, false) recipientDatabase.setBlocked(thread.recipient, false)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
binding.recyclerView.adapter!!.notifyDataSetChanged() binding.recyclerView.adapter?.notifyDataSetChanged()
dialog.dismiss() }
}
}
cancelButton()
} }
} }
}.show()
} }
private fun setConversationMuted(thread: ThreadRecord, isMuted: Boolean) { private fun setConversationMuted(thread: ThreadRecord, isMuted: Boolean) {
if (!isMuted) { if (isMuted) {
lifecycleScope.launch(Dispatchers.IO) {
recipientDatabase.setMuted(thread.recipient, 0)
withContext(Dispatchers.Main) {
binding.recyclerView.adapter!!.notifyDataSetChanged()
}
}
} else {
MuteDialog.show(this) { until: Long -> MuteDialog.show(this) { until: Long ->
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
recipientDatabase.setMuted(thread.recipient, until) recipientDatabase.setMuted(thread.recipient, until)
@ -536,6 +534,13 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
} }
} }
} }
} else {
lifecycleScope.launch(Dispatchers.IO) {
recipientDatabase.setMuted(thread.recipient, 0)
withContext(Dispatchers.Main) {
binding.recyclerView.adapter!!.notifyDataSetChanged()
}
}
} }
} }
@ -574,20 +579,28 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
} else { } else {
resources.getString(R.string.activity_home_delete_conversation_dialog_message) resources.getString(R.string.activity_home_delete_conversation_dialog_message)
} }
val dialog = AlertDialog.Builder(this)
dialog.setMessage(message) sessionDialog {
dialog.setPositiveButton(R.string.yes) { _, _ -> text(message)
buttons {
button(R.string.yes) {
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
val context = this@HomeActivity as Context val context = this@HomeActivity as Context
// Cancel any outstanding jobs // Cancel any outstanding jobs
DatabaseComponent.get(context).sessionJobDatabase().cancelPendingMessageSendJobs(threadID) DatabaseComponent.get(context).sessionJobDatabase()
.cancelPendingMessageSendJobs(threadID)
// Send a leave group message if this is an active closed group // Send a leave group message if this is an active closed group
if (recipient.address.isClosedGroup && DatabaseComponent.get(context).groupDatabase().isActive(recipient.address.toGroupString())) { if (recipient.address.isClosedGroup && DatabaseComponent.get(context)
.groupDatabase().isActive(recipient.address.toGroupString())
) {
var isClosedGroup: Boolean var isClosedGroup: Boolean
var groupPublicKey: String? var groupPublicKey: String?
try { try {
groupPublicKey = GroupUtil.doubleDecodeGroupID(recipient.address.toString()).toHexString() groupPublicKey =
isClosedGroup = DatabaseComponent.get(context).lokiAPIDatabase().isClosedGroup(groupPublicKey) GroupUtil.doubleDecodeGroupID(recipient.address.toString())
.toHexString()
isClosedGroup = DatabaseComponent.get(context).lokiAPIDatabase()
.isClosedGroup(groupPublicKey)
} catch (e: IOException) { } catch (e: IOException) {
groupPublicKey = null groupPublicKey = null
isClosedGroup = false isClosedGroup = false
@ -597,25 +610,33 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
} }
} }
// Delete the conversation // Delete the conversation
val v2OpenGroup = DatabaseComponent.get(this@HomeActivity).lokiThreadDatabase().getOpenGroupChat(threadID) val v2OpenGroup =
DatabaseComponent.get(this@HomeActivity).lokiThreadDatabase()
.getOpenGroupChat(threadID)
if (v2OpenGroup != null) { if (v2OpenGroup != null) {
OpenGroupManager.delete(v2OpenGroup.server, v2OpenGroup.room, this@HomeActivity) OpenGroupManager.delete(
v2OpenGroup.server,
v2OpenGroup.room,
this@HomeActivity
)
} else { } else {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
threadDb.deleteConversation(threadID) threadDb.deleteConversation(threadID)
} }
} }
// Update the badge count // Update the badge count
ApplicationContext.getInstance(context).messageNotifier.updateNotification(context) ApplicationContext.getInstance(context).messageNotifier.updateNotification(
context
)
// Notify the user // Notify the user
val toastMessage = if (recipient.isGroupRecipient) R.string.MessageRecord_left_group else R.string.activity_home_conversation_deleted_message val toastMessage =
if (recipient.isGroupRecipient) R.string.MessageRecord_left_group else R.string.activity_home_conversation_deleted_message
Toast.makeText(context, toastMessage, Toast.LENGTH_LONG).show() Toast.makeText(context, toastMessage, Toast.LENGTH_LONG).show()
} }
} }
dialog.setNegativeButton(R.string.no) { _, _ -> button(R.string.no)
// Do nothing }
} }
dialog.create().show()
} }
private fun openSettings() { private fun openSettings() {
@ -624,22 +645,21 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
} }
private fun showMessageRequests() { private fun showMessageRequests() {
val intent = Intent(this, MessageRequestsActivity::class.java) Intent(this, MessageRequestsActivity::class.java).let(::push)
push(intent)
} }
private fun hideMessageRequests() { private fun hideMessageRequests() {
AlertDialog.Builder(this) sessionDialog {
.setMessage("Hide message requests?") text("Hide message requests?")
.setPositiveButton(R.string.yes) { _, _ -> buttons {
button(R.string.yes) {
textSecurePreferences.setHasHiddenMessageRequests() textSecurePreferences.setHasHiddenMessageRequests()
setupMessageRequestsBanner() setupMessageRequestsBanner()
homeViewModel.tryUpdateChannel() homeViewModel.tryUpdateChannel()
} }
.setNegativeButton(R.string.no) { _, _ -> button(R.string.no)
// Do nothing }
} }
.create().show()
} }
private fun showNewConversation() { private fun showNewConversation() {

View File

@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.sessionDialog
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.push
import javax.inject.Inject import javax.inject.Inject
@ -77,34 +78,38 @@ class MessageRequestsActivity : PassphraseRequiredActionBarActivity(), Conversat
} }
override fun onBlockConversationClick(thread: ThreadRecord) { override fun onBlockConversationClick(thread: ThreadRecord) {
val dialog = AlertDialog.Builder(this) fun doBlock() {
dialog.setTitle(R.string.RecipientPreferenceActivity_block_this_contact_question)
.setMessage(R.string.message_requests_block_message)
.setPositiveButton(R.string.recipient_preferences__block) { _, _ ->
viewModel.blockMessageRequest(thread) viewModel.blockMessageRequest(thread)
LoaderManager.getInstance(this).restartLoader(0, null, this) LoaderManager.getInstance(this).restartLoader(0, null, this)
} }
.setNegativeButton(R.string.no) { _, _ ->
// Do nothing sessionDialog {
title(R.string.RecipientPreferenceActivity_block_this_contact_question)
text(R.string.message_requests_block_message)
buttons {
button(R.string.recipient_preferences__block) { doBlock() }
button(R.string.no)
}
} }
dialog.create().show()
} }
override fun onDeleteConversationClick(thread: ThreadRecord) { override fun onDeleteConversationClick(thread: ThreadRecord) {
val dialog = AlertDialog.Builder(this) fun doDecline() {
dialog.setTitle(R.string.decline)
.setMessage(resources.getString(R.string.message_requests_decline_message))
.setPositiveButton(R.string.decline) { _,_ ->
viewModel.deleteMessageRequest(thread) viewModel.deleteMessageRequest(thread)
LoaderManager.getInstance(this).restartLoader(0, null, this) LoaderManager.getInstance(this).restartLoader(0, null, this)
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@MessageRequestsActivity) ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@MessageRequestsActivity)
} }
} }
.setNegativeButton(R.string.no) { _, _ ->
// Do nothing sessionDialog {
title(R.string.decline)
text(resources.getString(R.string.message_requests_decline_message))
buttons {
button(R.string.decline) { doDecline() }
button(R.string.no)
}
} }
dialog.create().show()
} }
private fun updateEmptyState() { private fun updateEmptyState() {
@ -114,18 +119,20 @@ class MessageRequestsActivity : PassphraseRequiredActionBarActivity(), Conversat
} }
private fun deleteAllAndBlock() { private fun deleteAllAndBlock() {
val dialog = AlertDialog.Builder(this) fun doDeleteAllAndBlock() {
dialog.setMessage(resources.getString(R.string.message_requests_clear_all_message))
dialog.setPositiveButton(R.string.yes) { _, _ ->
viewModel.clearAllMessageRequests() viewModel.clearAllMessageRequests()
LoaderManager.getInstance(this).restartLoader(0, null, this) LoaderManager.getInstance(this).restartLoader(0, null, this)
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@MessageRequestsActivity) ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@MessageRequestsActivity)
} }
} }
dialog.setNegativeButton(R.string.no) { _, _ ->
// Do nothing sessionDialog {
text(resources.getString(R.string.message_requests_clear_all_message))
buttons {
button(R.string.yes) { doDeleteAllAndBlock() }
button(R.string.no)
}
} }
dialog.create().show()
} }
} }

View File

@ -20,6 +20,7 @@ import org.session.libsession.utilities.ThemeUtil
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.home.HomeActivity import org.thoughtcrime.securesms.home.HomeActivity
import org.thoughtcrime.securesms.sessionDialog
import org.thoughtcrime.securesms.util.GlowViewUtilities import org.thoughtcrime.securesms.util.GlowViewUtilities
import org.thoughtcrime.securesms.util.PNModeView import org.thoughtcrime.securesms.util.PNModeView
import org.thoughtcrime.securesms.util.disableClipping import org.thoughtcrime.securesms.util.disableClipping
@ -151,12 +152,15 @@ class PNModeActivity : BaseActionBarActivity() {
private fun register() { private fun register() {
if (selectedOptionView == null) { if (selectedOptionView == null) {
val dialog = AlertDialog.Builder(this) sessionDialog {
dialog.setTitle(R.string.activity_pn_mode_no_option_picked_dialog_title) title(R.string.activity_pn_mode_no_option_picked_dialog_title)
dialog.setPositiveButton(R.string.ok) { _, _ -> } buttons {
dialog.create().show() button(R.string.ok)
}
}
return return
} }
TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == binding.fcmOptionView)) TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == binding.fcmOptionView))
val application = ApplicationContext.getInstance(this) val application = ApplicationContext.getInstance(this)
application.startPollingIfNeeded() application.startPollingIfNeeded()

View File

@ -9,6 +9,7 @@ import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityBlockedContactsBinding import network.loki.messenger.databinding.ActivityBlockedContactsBinding
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.sessionDialog
@AndroidEntryPoint @AndroidEntryPoint
class BlockedContactsActivity: PassphraseRequiredActionBarActivity(), View.OnClickListener { class BlockedContactsActivity: PassphraseRequiredActionBarActivity(), View.OnClickListener {
@ -51,17 +52,14 @@ class BlockedContactsActivity: PassphraseRequiredActionBarActivity(), View.OnCli
getString(R.string.Unblock_dialog__message, stringBuilder.toString()) getString(R.string.Unblock_dialog__message, stringBuilder.toString())
} }
AlertDialog.Builder(this) sessionDialog {
.setTitle(title) title(title)
.setMessage(message) text(message)
.setPositiveButton(R.string.continue_2) { d, _ -> buttons {
viewModel.unblock(contactsToUnblock) button(R.string.continue_2) { viewModel.unblock(contactsToUnblock) }
d.dismiss() cancelButton()
} }
.setNegativeButton(R.string.cancel) { d, _ ->
d.dismiss()
} }
.show()
} }
} }

View File

@ -1,41 +1,24 @@
package org.thoughtcrime.securesms.preferences package org.thoughtcrime.securesms.preferences
import android.content.Context import android.content.Context
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.preference.ListPreference import androidx.preference.ListPreference
import network.loki.messenger.databinding.DialogListPreferenceBinding import org.thoughtcrime.securesms.sessionDialog
fun listPreferenceDialog( fun listPreferenceDialog(
context: Context, context: Context,
listPreference: ListPreference, listPreference: ListPreference,
dialogListener: () -> Unit onChange: () -> Unit
) : AlertDialog { ) : AlertDialog = listPreference.run {
context.sessionDialog {
val index = entryValues.indexOf(value)
val options = entries.map(CharSequence::toString).toTypedArray()
val builder = AlertDialog.Builder(context) title(dialogTitle)
text(dialogMessage)
val binding = DialogListPreferenceBinding.inflate(LayoutInflater.from(context)) singleChoiceItems(options, index) {
binding.titleTextView.text = listPreference.dialogTitle listPreference.setValueIndex(it)
binding.messageTextView.text = listPreference.dialogMessage onChange()
builder.setView(binding.root)
val dialog = builder.show()
val valueIndex = listPreference.findIndexOfValue(listPreference.value)
RadioOptionAdapter(valueIndex) {
listPreference.value = it.value
dialog.dismiss()
dialogListener()
} }
.apply {
listPreference.entryValues.zip(listPreference.entries) { value, title ->
RadioOption(value.toString(), title.toString())
}.let(this::submitList)
} }
.let { binding.recyclerView.adapter = it }
binding.closeButton.setOnClickListener { dialog.dismiss() }
return dialog
} }

View File

@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.preferences.appearance.AppearanceSettingsActivity import org.thoughtcrime.securesms.preferences.appearance.AppearanceSettingsActivity
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints
import org.thoughtcrime.securesms.sessionDialog
import org.thoughtcrime.securesms.util.BitmapDecodingException import org.thoughtcrime.securesms.util.BitmapDecodingException
import org.thoughtcrime.securesms.util.BitmapUtil import org.thoughtcrime.securesms.util.BitmapUtil
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
@ -260,19 +261,17 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
} }
private fun showEditProfilePictureUI() { private fun showEditProfilePictureUI() {
AlertDialog.Builder(this) sessionDialog {
.setTitle(R.string.activity_settings_set_display_picture) title(R.string.activity_settings_set_display_picture)
.setView(R.layout.dialog_change_avatar) view(R.layout.dialog_change_avatar)
.setPositiveButton(R.string.activity_settings_upload) { _, _ -> buttons {
startAvatarSelection() button(R.string.activity_settings_upload) { startAvatarSelection() }
}
.setNegativeButton(R.string.cancel) { _, _ -> }
.apply {
if (TextSecurePreferences.getProfileAvatarId(context) != 0) { if (TextSecurePreferences.getProfileAvatarId(context) != 0) {
setNeutralButton(R.string.activity_settings_remove) { _, _ -> removeAvatar() } button(R.string.activity_settings_remove) { removeAvatar() }
} }
cancelButton()
} }
.show().apply { }.apply {
findViewById<ProfilePictureView>(R.id.profile_picture_view)?.let(::setupProfilePictureView) findViewById<ProfilePictureView>(R.id.profile_picture_view)?.let(::setupProfilePictureView)
} }
} }

View File

@ -18,6 +18,7 @@ import org.session.libsession.utilities.task.ProgressDialogAsyncTask
import org.session.libsignal.utilities.ExternalStorageUtil import org.session.libsignal.utilities.ExternalStorageUtil
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.sessionDialog
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
@ -41,18 +42,19 @@ class SaveAttachmentTask : ProgressDialogAsyncTask<SaveAttachmentTask.Attachment
@JvmStatic @JvmStatic
@JvmOverloads @JvmOverloads
fun showWarningDialog(context: Context, onAcceptListener: OnClickListener, count: Int = 1) { fun showWarningDialog(context: Context, count: Int = 1, onAcceptListener: () -> Unit) {
val builder = AlertDialog.Builder(context) context.sessionDialog {
builder.setTitle(R.string.ConversationFragment_save_to_sd_card) title(R.string.ConversationFragment_save_to_sd_card)
builder.setIconAttribute(R.attr.dialog_alert_icon) setIconAttribute(R.attr.dialog_alert_icon)
builder.setCancelable(true) text(context.resources.getQuantityString(
builder.setMessage(context.resources.getQuantityString(
R.plurals.ConversationFragment_saving_n_media_to_storage_warning, R.plurals.ConversationFragment_saving_n_media_to_storage_warning,
count, count,
count)) count))
builder.setPositiveButton(R.string.yes, onAcceptListener) buttons {
builder.setNegativeButton(R.string.no, null) button(R.string.yes) { onAcceptListener() }
builder.show() button(R.string.no)
}
}
} }
} }