mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-17 23:43:34 +01:00
Add custom mute until option.
This commit is contained in:
committed by
Cody Henthorne
parent
7d1897a9d2
commit
121f0c6134
@@ -1,65 +0,0 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MuteDialog extends AlertDialog {
|
||||
|
||||
|
||||
protected MuteDialog(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
protected MuteDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
|
||||
super(context, cancelable, cancelListener);
|
||||
}
|
||||
|
||||
protected MuteDialog(Context context, int theme) {
|
||||
super(context, theme);
|
||||
}
|
||||
|
||||
public static void show(final Context context, final @NonNull MuteSelectionListener listener) {
|
||||
show(context, listener, null);
|
||||
}
|
||||
|
||||
public static void show(final Context context, final @NonNull MuteSelectionListener listener, @Nullable Runnable cancelListener) {
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context);
|
||||
builder.setTitle(R.string.MuteDialog_mute_notifications);
|
||||
builder.setItems(R.array.mute_durations, (dialog, which) -> {
|
||||
final long muteUntil;
|
||||
|
||||
switch (which) {
|
||||
case 0: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
|
||||
case 1: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(8); break;
|
||||
case 2: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); break;
|
||||
case 3: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7); break;
|
||||
case 4: muteUntil = Long.MAX_VALUE; break;
|
||||
default: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
|
||||
}
|
||||
|
||||
listener.onMuted(muteUntil);
|
||||
});
|
||||
|
||||
if (cancelListener != null) {
|
||||
builder.setOnCancelListener(dialog -> {
|
||||
cancelListener.run();
|
||||
dialog.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
builder.show();
|
||||
|
||||
}
|
||||
|
||||
public interface MuteSelectionListener {
|
||||
public void onMuted(long until);
|
||||
}
|
||||
|
||||
}
|
||||
73
app/src/main/java/org/thoughtcrime/securesms/MuteDialog.kt
Normal file
73
app/src/main/java/org/thoughtcrime/securesms/MuteDialog.kt
Normal file
@@ -0,0 +1,73 @@
|
||||
package org.thoughtcrime.securesms
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.thoughtcrime.securesms.components.settings.conversation.MuteUntilTimePickerBottomSheet
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
|
||||
object MuteDialog {
|
||||
|
||||
private const val MUTE_UNTIL: Long = -1L
|
||||
|
||||
private data class MuteOption(
|
||||
@DrawableRes val iconRes: Int,
|
||||
val title: String,
|
||||
val duration: Long
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
fun show(context: Context, fragmentManager: FragmentManager, lifecycleOwner: LifecycleOwner, action: MuteSelectionListener) {
|
||||
fragmentManager.setFragmentResultListener(MuteUntilTimePickerBottomSheet.REQUEST_KEY, lifecycleOwner) { _, bundle ->
|
||||
action.onMuted(bundle.getLong(MuteUntilTimePickerBottomSheet.RESULT_TIMESTAMP))
|
||||
}
|
||||
|
||||
val options = listOf(
|
||||
MuteOption(R.drawable.ic_daytime_24, context.getString(R.string.arrays__mute_for_one_hour), 1.hours.inWholeMilliseconds),
|
||||
MuteOption(R.drawable.ic_nighttime_26, context.getString(R.string.arrays__mute_for_eight_hours), 8.hours.inWholeMilliseconds),
|
||||
MuteOption(R.drawable.symbol_calendar_one, context.getString(R.string.arrays__mute_for_one_day), 1.days.inWholeMilliseconds),
|
||||
MuteOption(R.drawable.symbol_calendar_week, context.getString(R.string.arrays__mute_for_seven_days), 7.days.inWholeMilliseconds),
|
||||
MuteOption(R.drawable.symbol_calendar_24, context.getString(R.string.MuteDialog__mute_until), MUTE_UNTIL),
|
||||
MuteOption(R.drawable.symbol_bell_slash_24, context.getString(R.string.arrays__always), Long.MAX_VALUE)
|
||||
)
|
||||
|
||||
val adapter = object : BaseAdapter() {
|
||||
override fun getCount(): Int = options.size
|
||||
override fun getItem(position: Int): MuteOption = options[position]
|
||||
override fun getItemId(position: Int): Long = position.toLong()
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val view = convertView ?: LayoutInflater.from(context).inflate(R.layout.mute_dialog_item, parent, false)
|
||||
val option = options[position]
|
||||
view.findViewById<ImageView>(R.id.mute_dialog_icon).setImageResource(option.iconRes)
|
||||
view.findViewById<TextView>(R.id.mute_dialog_title).text = option.title
|
||||
return view
|
||||
}
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.MuteDialog_mute_notifications)
|
||||
.setAdapter(adapter) { _, which ->
|
||||
val option = options[which]
|
||||
when (option.duration) {
|
||||
MUTE_UNTIL -> MuteUntilTimePickerBottomSheet.show(fragmentManager)
|
||||
Long.MAX_VALUE -> action.onMuted(Long.MAX_VALUE)
|
||||
else -> action.onMuted(System.currentTimeMillis() + option.duration)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
fun interface MuteSelectionListener {
|
||||
fun onMuted(until: Long)
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,6 @@ import org.signal.core.util.orNull
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.AvatarPreviewActivity
|
||||
import org.thoughtcrime.securesms.BlockUnblockDialog
|
||||
import org.thoughtcrime.securesms.MuteDialog
|
||||
import org.thoughtcrime.securesms.PushContactSelectionActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
@@ -119,10 +118,11 @@ private const val REQUEST_CODE_ADD_CONTACT = 2
|
||||
private const val REQUEST_CODE_ADD_MEMBERS_TO_GROUP = 3
|
||||
private const val REQUEST_CODE_RETURN_FROM_MEDIA = 4
|
||||
|
||||
class ConversationSettingsFragment : DSLSettingsFragment(
|
||||
layoutId = R.layout.conversation_settings_fragment,
|
||||
menuId = R.menu.conversation_settings
|
||||
) {
|
||||
class ConversationSettingsFragment :
|
||||
DSLSettingsFragment(
|
||||
layoutId = R.layout.conversation_settings_fragment,
|
||||
menuId = R.menu.conversation_settings
|
||||
) {
|
||||
|
||||
private val args: ConversationSettingsFragmentArgs by navArgs()
|
||||
private val alertTint by lazy { ContextCompat.getColor(requireContext(), R.color.signal_alert_primary) }
|
||||
@@ -469,9 +469,11 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
||||
YouAreAlreadyInACallSnackbar.show(requireView())
|
||||
}
|
||||
},
|
||||
onMuteClick = {
|
||||
onMuteClick = { view ->
|
||||
if (!state.buttonStripState.isMuted) {
|
||||
MuteDialog.show(requireContext(), viewModel::setMuteUntil)
|
||||
MuteContextMenu.show(view, requireView() as ViewGroup, childFragmentManager, viewLifecycleOwner) { duration ->
|
||||
viewModel.setMuteUntil(duration)
|
||||
}
|
||||
} else {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(state.recipient.muteUntil.formatMutedUntil(requireContext()))
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.thoughtcrime.securesms.components.settings.conversation
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import org.signal.core.util.dp
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.menu.ActionItem
|
||||
import org.thoughtcrime.securesms.components.menu.SignalContextMenu
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object MuteContextMenu {
|
||||
|
||||
@JvmStatic
|
||||
fun show(anchor: View, container: ViewGroup, fragmentManager: FragmentManager, lifecycleOwner: LifecycleOwner, action: (Long) -> Unit): SignalContextMenu {
|
||||
fragmentManager.setFragmentResultListener(MuteUntilTimePickerBottomSheet.REQUEST_KEY, lifecycleOwner) { _, bundle ->
|
||||
action(bundle.getLong(MuteUntilTimePickerBottomSheet.RESULT_TIMESTAMP))
|
||||
}
|
||||
|
||||
val context = anchor.context
|
||||
val actionItems = listOf(
|
||||
ActionItem(R.drawable.ic_daytime_24, context.getString(R.string.arrays__mute_for_one_hour)) {
|
||||
action(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1))
|
||||
},
|
||||
ActionItem(R.drawable.ic_nighttime_26, context.getString(R.string.arrays__mute_for_eight_hours)) {
|
||||
action(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(8))
|
||||
},
|
||||
ActionItem(R.drawable.symbol_calendar_one, context.getString(R.string.arrays__mute_for_one_day)) {
|
||||
action(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1))
|
||||
},
|
||||
ActionItem(R.drawable.symbol_calendar_week, context.getString(R.string.arrays__mute_for_seven_days)) {
|
||||
action(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7))
|
||||
},
|
||||
ActionItem(R.drawable.symbol_calendar_24, context.getString(R.string.MuteDialog__mute_until)) {
|
||||
MuteUntilTimePickerBottomSheet.show(fragmentManager)
|
||||
},
|
||||
ActionItem(R.drawable.symbol_bell_slash_24, context.getString(R.string.arrays__always)) {
|
||||
action(Long.MAX_VALUE)
|
||||
}
|
||||
)
|
||||
|
||||
return SignalContextMenu.Builder(anchor, container)
|
||||
.offsetX(12.dp)
|
||||
.offsetY(12.dp)
|
||||
.preferredVerticalPosition(SignalContextMenu.VerticalPosition.ABOVE)
|
||||
.show(actionItems)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package org.thoughtcrime.securesms.components.settings.conversation
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.setFragmentResult
|
||||
import com.google.android.material.datepicker.CalendarConstraints
|
||||
import com.google.android.material.datepicker.DateValidatorPointForward
|
||||
import com.google.android.material.datepicker.MaterialDatePicker
|
||||
import com.google.android.material.timepicker.MaterialTimePicker
|
||||
import com.google.android.material.timepicker.TimeFormat
|
||||
import org.signal.core.ui.BottomSheetUtil
|
||||
import org.signal.core.ui.compose.BottomSheets
|
||||
import org.signal.core.ui.compose.Buttons
|
||||
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
|
||||
import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.atMidnight
|
||||
import org.thoughtcrime.securesms.util.atUTC
|
||||
import org.thoughtcrime.securesms.util.formatHours
|
||||
import org.thoughtcrime.securesms.util.toLocalDateTime
|
||||
import org.thoughtcrime.securesms.util.toMillis
|
||||
import java.time.DayOfWeek
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.temporal.TemporalAdjusters
|
||||
import java.util.Locale
|
||||
|
||||
class MuteUntilTimePickerBottomSheet : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
override val peekHeightPercentage: Float = 0.66f
|
||||
|
||||
companion object {
|
||||
const val REQUEST_KEY = "mute_until_result"
|
||||
const val RESULT_TIMESTAMP = "timestamp"
|
||||
|
||||
@JvmStatic
|
||||
fun show(fragmentManager: FragmentManager) {
|
||||
val fragment = MuteUntilTimePickerBottomSheet()
|
||||
fragment.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun SheetContent() {
|
||||
val context = LocalContext.current
|
||||
val now = remember { LocalDateTime.now() }
|
||||
|
||||
val defaultDateTime = remember {
|
||||
if (now.hour < 17) {
|
||||
now.withHour(17).withMinute(0).withSecond(0).withNano(0)
|
||||
} else {
|
||||
val nextMorning = if (now.dayOfWeek == DayOfWeek.FRIDAY || now.dayOfWeek == DayOfWeek.SATURDAY || now.dayOfWeek == DayOfWeek.SUNDAY) {
|
||||
now.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
|
||||
} else {
|
||||
now.plusDays(1)
|
||||
}
|
||||
nextMorning.withHour(8).withMinute(0).withSecond(0).withNano(0)
|
||||
}
|
||||
}
|
||||
|
||||
var selectedDate by remember { mutableLongStateOf(defaultDateTime.toMillis()) }
|
||||
var selectedHour by remember { mutableIntStateOf(defaultDateTime.hour) }
|
||||
var selectedMinute by remember { mutableIntStateOf(defaultDateTime.minute) }
|
||||
|
||||
val dateText = remember(selectedDate) {
|
||||
DateUtils.getDayPrecisionTimeString(context, Locale.getDefault(), selectedDate)
|
||||
}
|
||||
|
||||
val timeText = remember(selectedHour, selectedMinute) {
|
||||
LocalTime.of(selectedHour, selectedMinute).formatHours(context)
|
||||
}
|
||||
|
||||
val zonedDateTime = remember { ZonedDateTime.now() }
|
||||
val timezoneDisclaimer = remember {
|
||||
val zoneOffsetFormatter = DateTimeFormatter.ofPattern("OOOO")
|
||||
val zoneNameFormatter = DateTimeFormatter.ofPattern("zzzz")
|
||||
context.getString(
|
||||
R.string.MuteUntilTimePickerBottomSheet__timezone_disclaimer,
|
||||
zoneOffsetFormatter.format(zonedDateTime),
|
||||
zoneNameFormatter.format(zonedDateTime)
|
||||
)
|
||||
}
|
||||
|
||||
MuteUntilSheetContent(
|
||||
dateText = dateText,
|
||||
timeText = timeText,
|
||||
timezoneDisclaimer = timezoneDisclaimer,
|
||||
onDateClick = {
|
||||
val local = LocalDateTime.now().atMidnight().atUTC().toMillis()
|
||||
val datePicker = MaterialDatePicker.Builder.datePicker()
|
||||
.setTitleText(context.getString(R.string.MuteUntilTimePickerBottomSheet__select_date_title))
|
||||
.setSelection(selectedDate)
|
||||
.setCalendarConstraints(CalendarConstraints.Builder().setStart(local).setValidator(DateValidatorPointForward.now()).build())
|
||||
.build()
|
||||
|
||||
datePicker.addOnDismissListener {
|
||||
datePicker.clearOnDismissListeners()
|
||||
datePicker.clearOnPositiveButtonClickListeners()
|
||||
}
|
||||
|
||||
datePicker.addOnPositiveButtonClickListener {
|
||||
selectedDate = it.toLocalDateTime(ZoneOffset.UTC).atZone(ZoneId.systemDefault()).toMillis()
|
||||
}
|
||||
datePicker.show(childFragmentManager, "DATE_PICKER")
|
||||
},
|
||||
onTimeClick = {
|
||||
val timeFormat = if (DateFormat.is24HourFormat(context)) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H
|
||||
val timePicker = MaterialTimePicker.Builder()
|
||||
.setTimeFormat(timeFormat)
|
||||
.setHour(selectedHour)
|
||||
.setMinute(selectedMinute)
|
||||
.setTitleText(context.getString(R.string.MuteUntilTimePickerBottomSheet__select_time_title))
|
||||
.build()
|
||||
|
||||
timePicker.addOnDismissListener {
|
||||
timePicker.clearOnDismissListeners()
|
||||
timePicker.clearOnPositiveButtonClickListeners()
|
||||
}
|
||||
|
||||
timePicker.addOnPositiveButtonClickListener {
|
||||
selectedHour = timePicker.hour
|
||||
selectedMinute = timePicker.minute
|
||||
}
|
||||
timePicker.show(childFragmentManager, "TIME_PICKER")
|
||||
},
|
||||
onMuteClick = {
|
||||
val timestamp = selectedDate.toLocalDateTime()
|
||||
.withHour(selectedHour)
|
||||
.withMinute(selectedMinute)
|
||||
.withSecond(0)
|
||||
.withNano(0)
|
||||
.toMillis()
|
||||
|
||||
if (timestamp > System.currentTimeMillis()) {
|
||||
setFragmentResult(REQUEST_KEY, bundleOf(RESULT_TIMESTAMP to timestamp))
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MuteUntilSheetContent(
|
||||
dateText: String,
|
||||
timeText: String,
|
||||
timezoneDisclaimer: String,
|
||||
onDateClick: () -> Unit,
|
||||
onTimeClick: () -> Unit,
|
||||
onMuteClick: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
BottomSheets.Handle()
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.MuteUntilTimePickerBottomSheet__dialog_title),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(top = 18.dp, bottom = 24.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = timezoneDisclaimer,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 56.dp)
|
||||
.align(Alignment.Start)
|
||||
)
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.clickable(onClick = onDateClick)
|
||||
) {
|
||||
Text(
|
||||
text = dateText,
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_expand_down_24),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(24.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.clickable(onClick = onTimeClick)
|
||||
) {
|
||||
Text(
|
||||
text = timeText,
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_expand_down_24),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 18.dp, end = 18.dp, top = 14.dp, bottom = 24.dp)
|
||||
) {
|
||||
Buttons.MediumTonal(
|
||||
onClick = onMuteClick
|
||||
) {
|
||||
Text(stringResource(R.string.MuteUntilTimePickerBottomSheet__mute_notifications))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun MuteUntilSheetContentPreview() {
|
||||
Previews.BottomSheetContentPreview {
|
||||
MuteUntilSheetContent(
|
||||
dateText = "Today",
|
||||
timeText = "5:00 PM",
|
||||
timezoneDisclaimer = "All times in (GMT-05:00) Eastern Standard Time",
|
||||
onDateClick = {},
|
||||
onTimeClick = {},
|
||||
onMuteClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ object ButtonStripPreference {
|
||||
val onMessageClick: () -> Unit = {},
|
||||
val onVideoClick: () -> Unit = {},
|
||||
val onAudioClick: () -> Unit = {},
|
||||
val onMuteClick: () -> Unit = {},
|
||||
val onMuteClick: (View) -> Unit = {},
|
||||
val onSearchClick: () -> Unit = {}
|
||||
) : PreferenceModel<Model>() {
|
||||
override fun areContentsTheSame(newItem: Model): Boolean {
|
||||
@@ -97,7 +97,7 @@ object ButtonStripPreference {
|
||||
message.setOnClickListener { model.onMessageClick() }
|
||||
videoCall.setOnClickListener { model.onVideoClick() }
|
||||
audioCall.setOnClickListener { model.onAudioClick() }
|
||||
mute.setOnClickListener { model.onMuteClick() }
|
||||
mute.setOnClickListener { model.onMuteClick(it) }
|
||||
search.setOnClickListener { model.onSearchClick() }
|
||||
addToStory.setOnClickListener { model.onAddToStoryClick() }
|
||||
}
|
||||
|
||||
@@ -16,9 +16,10 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
class SoundsAndNotificationsSettingsFragment : DSLSettingsFragment(
|
||||
titleId = R.string.ConversationSettingsFragment__sounds_and_notifications
|
||||
) {
|
||||
class SoundsAndNotificationsSettingsFragment :
|
||||
DSLSettingsFragment(
|
||||
titleId = R.string.ConversationSettingsFragment__sounds_and_notifications
|
||||
) {
|
||||
|
||||
private val mentionLabels: Array<String> by lazy {
|
||||
resources.getStringArray(R.array.SoundsAndNotificationsSettingsFragment__mention_labels)
|
||||
@@ -66,7 +67,7 @@ class SoundsAndNotificationsSettingsFragment : DSLSettingsFragment(
|
||||
summary = DSLSettingsText.from(muteSummary),
|
||||
onClick = {
|
||||
if (state.muteUntil <= 0) {
|
||||
MuteDialog.show(requireContext(), viewModel::setMuteUntil)
|
||||
MuteDialog.show(requireContext(), childFragmentManager, viewLifecycleOwner, viewModel::setMuteUntil)
|
||||
} else {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(muteSummary)
|
||||
|
||||
@@ -3992,7 +3992,7 @@ class ConversationFragment :
|
||||
}
|
||||
|
||||
override fun handleMuteNotifications() {
|
||||
MuteDialog.show(requireContext(), viewModel::muteConversation)
|
||||
MuteDialog.show(requireContext(), childFragmentManager, viewLifecycleOwner, viewModel::muteConversation)
|
||||
}
|
||||
|
||||
override fun handleUnmuteNotifications() {
|
||||
|
||||
@@ -1177,9 +1177,7 @@ public class ConversationListFragment extends MainFragment implements Conversati
|
||||
}
|
||||
|
||||
private void handleMute(@NonNull Collection<Conversation> conversations) {
|
||||
MuteDialog.show(requireContext(), until -> {
|
||||
updateMute(conversations, until);
|
||||
});
|
||||
MuteDialog.show(requireContext(), getChildFragmentManager(), getViewLifecycleOwner(), until -> updateMute(conversations, until));
|
||||
}
|
||||
|
||||
private void handleUnmute(@NonNull Collection<Conversation> conversations) {
|
||||
@@ -1612,7 +1610,7 @@ public class ConversationListFragment extends MainFragment implements Conversati
|
||||
|
||||
@Override
|
||||
public void onMuteAll(@NonNull ChatFolderRecord chatFolder) {
|
||||
MuteDialog.show(requireContext(), until -> viewModel.onUpdateMute(chatFolder, until));
|
||||
MuteDialog.show(requireContext(), getChildFragmentManager(), getViewLifecycleOwner(), until -> viewModel.onUpdateMute(chatFolder, until));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1945,6 +1943,7 @@ public class ConversationListFragment extends MainFragment implements Conversati
|
||||
|
||||
void onMultiSelectFinished();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
12
app/src/main/res/drawable/symbol_calendar_one.xml
Normal file
12
app/src/main/res/drawable/symbol_calendar_one.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12.37 11.13c-0.23 0-0.43 0.1-0.57 0.25-0.22 0.25-0.42 0.4-0.64 0.5-0.22 0.11-0.5 0.18-0.86 0.22-0.37 0.05-0.68 0.37-0.68 0.76 0 0.42 0.35 0.77 0.77 0.77h0.98v4.12c0 0.48 0.4 0.88 0.88 0.88s0.88-0.4 0.88-0.88v-5.87c0-0.42-0.34-0.76-0.76-0.76Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M7.5 0.75c0.55 0 1 0.45 1 1v1.88h7V1.75c0-0.55 0.45-1 1-1s1 0.45 1 1v1.88c0.36 0 0.68 0 0.96 0.03 0.47 0.04 0.91 0.12 1.32 0.33 0.64 0.33 1.15 0.84 1.48 1.48 0.2 0.4 0.29 0.85 0.33 1.32 0.04 0.45 0.04 1 0.04 1.67v8.58c0 0.67 0 1.22-0.04 1.67-0.04 0.47-0.12 0.91-0.33 1.32-0.33 0.64-0.84 1.15-1.48 1.48-0.4 0.2-0.85 0.29-1.32 0.33-0.45 0.04-1 0.04-1.67 0.04H7.2c-0.67 0-1.22 0-1.67-0.04-0.47-0.04-0.91-0.12-1.32-0.33-0.64-0.33-1.15-0.84-1.48-1.48-0.2-0.4-0.29-0.85-0.33-1.32-0.04-0.45-0.04-1-0.04-1.67V8.46c0-0.67 0-1.22 0.04-1.67 0.04-0.47 0.12-0.91 0.33-1.32 0.33-0.64 0.84-1.15 1.48-1.48 0.4-0.2 0.85-0.29 1.32-0.33 0.28-0.02 0.6-0.03 0.96-0.03V1.75c0-0.55 0.45-1 1-1ZM5.68 5.41C5.32 5.44 5.14 5.49 5 5.55c-0.3 0.16-0.55 0.4-0.7 0.71-0.07 0.13-0.12 0.3-0.15 0.67-0.02 0.25-0.03 0.55-0.03 0.95h15.74c0-0.4 0-0.7-0.03-0.95-0.03-0.36-0.08-0.54-0.14-0.67-0.16-0.3-0.4-0.55-0.71-0.7-0.13-0.07-0.3-0.12-0.67-0.15-0.37-0.03-0.86-0.04-1.57-0.04h-9.5c-0.71 0-1.2 0-1.57 0.04Zm14.2 4.21H4.13V17c0 0.71 0 1.2 0.03 1.57 0.03 0.36 0.08 0.54 0.14 0.67 0.16 0.3 0.4 0.55 0.71 0.7 0.13 0.07 0.3 0.12 0.67 0.15 0.37 0.03 0.86 0.04 1.57 0.04h9.5c0.71 0 1.2 0 1.57-0.04 0.36-0.03 0.54-0.08 0.67-0.14 0.3-0.16 0.55-0.4 0.7-0.71 0.07-0.13 0.12-0.3 0.15-0.67 0.03-0.37 0.04-0.86 0.04-1.57V9.62Z"/>
|
||||
</vector>
|
||||
18
app/src/main/res/drawable/symbol_calendar_week.xml
Normal file
18
app/src/main/res/drawable/symbol_calendar_week.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M7.5 11.25c-0.76 0-1.38 0.62-1.38 1.38C6.13 13.38 6.75 14 7.5 14c0.76 0 1.38-0.62 1.38-1.38 0-0.75-0.62-1.37-1.38-1.37Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M16.5 11.25c-0.76 0-1.38 0.62-1.38 1.38 0 0.75 0.62 1.37 1.38 1.37 0.76 0 1.38-0.62 1.38-1.38 0-0.75-0.62-1.37-1.38-1.37Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12 11.25c-0.76 0-1.38 0.62-1.38 1.38 0 0.75 0.62 1.37 1.38 1.37 0.76 0 1.38-0.62 1.38-1.38 0-0.75-0.62-1.37-1.38-1.37Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M7.5 0.75c0.55 0 1 0.45 1 1v1.88h7V1.75c0-0.55 0.45-1 1-1s1 0.45 1 1v1.88c0.36 0 0.68 0 0.96 0.03 0.47 0.04 0.91 0.12 1.32 0.33 0.64 0.33 1.15 0.84 1.48 1.48 0.2 0.4 0.29 0.85 0.33 1.32 0.04 0.45 0.04 1 0.04 1.67v8.58c0 0.67 0 1.22-0.04 1.67-0.04 0.47-0.12 0.91-0.33 1.32-0.33 0.64-0.84 1.15-1.48 1.48-0.4 0.2-0.85 0.29-1.32 0.33-0.45 0.04-1 0.04-1.67 0.04H7.2c-0.67 0-1.22 0-1.67-0.04-0.47-0.04-0.91-0.12-1.32-0.33-0.64-0.33-1.15-0.84-1.48-1.48-0.2-0.4-0.29-0.85-0.33-1.32-0.04-0.45-0.04-1-0.04-1.67V8.46c0-0.67 0-1.22 0.04-1.67 0.04-0.47 0.12-0.91 0.33-1.32 0.33-0.64 0.84-1.15 1.48-1.48 0.4-0.2 0.85-0.29 1.32-0.33 0.28-0.02 0.6-0.03 0.96-0.03V1.75c0-0.55 0.45-1 1-1Zm-2.49 4.8c-0.3 0.16-0.55 0.4-0.7 0.71-0.07 0.13-0.12 0.3-0.15 0.67-0.02 0.25-0.03 0.55-0.03 0.95h15.74c0-0.4 0-0.7-0.03-0.95-0.03-0.36-0.08-0.54-0.14-0.67-0.16-0.3-0.4-0.55-0.71-0.7-0.13-0.07-0.3-0.12-0.67-0.15-0.37-0.03-0.86-0.04-1.57-0.04h-9.5c-0.71 0-1.2 0-1.57 0.04C5.32 5.44 5.14 5.49 5 5.55ZM4.13 9.63V17c0 0.71 0 1.2 0.03 1.57 0.03 0.36 0.08 0.54 0.14 0.67 0.16 0.3 0.4 0.55 0.71 0.7 0.13 0.07 0.3 0.12 0.67 0.15 0.37 0.03 0.86 0.04 1.57 0.04h9.5c0.71 0 1.2 0 1.57-0.04 0.36-0.03 0.54-0.08 0.67-0.14 0.3-0.16 0.55-0.4 0.7-0.71 0.07-0.13 0.12-0.3 0.15-0.67 0.03-0.37 0.04-0.86 0.04-1.57V9.62H4.13Z"/>
|
||||
</vector>
|
||||
27
app/src/main/res/layout/mute_dialog_item.xml
Normal file
27
app/src/main/res/layout/mute_dialog_item.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingVertical="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/mute_dialog_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="@drawable/ic_daytime_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mute_dialog_title"
|
||||
style="@style/Signal.Text.BodyLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
tools:text="Mute for 1 hour" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -3212,7 +3212,22 @@
|
||||
<string name="MessageDisplayHelper_message_encrypted_for_non_existing_session">Message encrypted for non-existing session</string>
|
||||
|
||||
<!-- MuteDialog -->
|
||||
<!-- Title for a dialog where a user chooses how long they'd like to mute notifications for -->
|
||||
<string name="MuteDialog_mute_notifications">Mute notifications</string>
|
||||
<!-- Dialog option that, when pressed, will open a time picker to let the user choose how long they'd like to mute notifications for -->
|
||||
<string name="MuteDialog__mute_until">Mute until…</string>
|
||||
|
||||
<!-- MuteUntilTimePickerBottomSheet -->
|
||||
<!-- Title for a dialog where a user chooses how long they'd like to mute notifications for -->
|
||||
<string name="MuteUntilTimePickerBottomSheet__dialog_title">Mute notifications until…</string>
|
||||
<!-- Label for a data selector where the user chooses how long they'd like to mute notifications for -->
|
||||
<string name="MuteUntilTimePickerBottomSheet__select_date_title">Select date</string>
|
||||
<!-- Label for a time selector where the user chooses how long they'd like to mute notifications for -->
|
||||
<string name="MuteUntilTimePickerBottomSheet__select_time_title">Select time</string>
|
||||
<!-- Label for a button that, when pressed, will confirm the user's choice on how long to mute notifications for -->
|
||||
<string name="MuteUntilTimePickerBottomSheet__mute_notifications">Mute notifications</string>
|
||||
<!-- Subtitle in a dialog that describes the timezone the user is picking times in. The first placeholder is a UTC offset, and the second placeholder is a user-friendly name for the timezone, e.g. "All times in (GMT-05:00) Eastern Standard Time" -->
|
||||
<string name="MuteUntilTimePickerBottomSheet__timezone_disclaimer">All times in (%1$s) %2$s</string>
|
||||
|
||||
<!-- KeyCachingService -->
|
||||
<string name="KeyCachingService_signal_passphrase_cached">Touch to open.</string>
|
||||
|
||||
Reference in New Issue
Block a user