Fix various issues regarding Notification Profile scheduling.

- Timezone conversion when detecting scheduled profile
- Not automatically enabling a scheduled profile on creation regardless
  of when other profiles were enabled/disabled
This commit is contained in:
Cody Henthorne
2021-12-08 17:28:36 -05:00
parent 372b0d9f2b
commit a8a104242a
11 changed files with 126 additions and 104 deletions

View File

@@ -1,10 +1,10 @@
package org.thoughtcrime.securesms.components.settings.app.notifications.manual
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import java.util.Calendar
import java.time.LocalDateTime
data class NotificationProfileSelectionState(
val notificationProfiles: List<NotificationProfile> = listOf(),
val expandedId: Long = -1L,
val timeSlotB: Calendar
val timeSlotB: LocalDateTime
)

View File

@@ -9,8 +9,10 @@ import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.kotlin.subscribeBy
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.NotificationProfilesRepository
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.util.isBetween
import org.thoughtcrime.securesms.util.livedata.Store
import java.util.Calendar
import org.thoughtcrime.securesms.util.toMillis
import java.time.LocalDateTime
import java.util.concurrent.TimeUnit
class NotificationProfileSelectionViewModel(private val repository: NotificationProfilesRepository) : ViewModel() {
@@ -53,29 +55,24 @@ class NotificationProfileSelectionViewModel(private val repository: Notification
.subscribe()
}
fun enableUntil(profile: NotificationProfile, calendar: Calendar) {
disposables += repository.manuallyEnableProfileForDuration(profile.id, calendar.timeInMillis)
fun enableUntil(profile: NotificationProfile, enableUntil: LocalDateTime) {
disposables += repository.manuallyEnableProfileForDuration(profile.id, enableUntil.toMillis())
.subscribe()
}
companion object {
private fun getTimeSlotB(): Calendar {
val now = Calendar.getInstance()
val sixPm = Calendar.getInstance()
val eightAm = Calendar.getInstance()
@Suppress("CascadeIf")
private fun getTimeSlotB(): LocalDateTime {
val now = LocalDateTime.now()
val sixPm = now.withHour(18).withMinute(0).withSecond(0)
val eightAm = now.withHour(8).withMinute(0).withSecond(0)
sixPm.set(Calendar.HOUR_OF_DAY, 18)
sixPm.set(Calendar.MINUTE, 0)
sixPm.set(Calendar.SECOND, 0)
eightAm.set(Calendar.HOUR_OF_DAY, 8)
eightAm.set(Calendar.MINUTE, 0)
eightAm.set(Calendar.SECOND, 0)
return if (now.before(sixPm) && (now.after(eightAm) || now == eightAm)) {
return if (now.isBetween(eightAm, sixPm)) {
sixPm
} else {
} else if (now.isBefore(eightAm)) {
eightAm
} else {
eightAm.plusDays(1)
}
}
}

View File

@@ -9,12 +9,12 @@ import org.thoughtcrime.securesms.components.emoji.EmojiImageView
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.PreferenceModel
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.MappingAdapter
import org.thoughtcrime.securesms.util.MappingViewHolder
import org.thoughtcrime.securesms.util.formatHours
import org.thoughtcrime.securesms.util.visible
import java.util.Calendar
import java.util.Locale
import java.time.LocalDateTime
import java.time.LocalTime
/**
* Notification Profile selection preference.
@@ -34,10 +34,10 @@ object NotificationProfileSelection {
override val summary: DSLSettingsText,
val notificationProfile: NotificationProfile,
val isExpanded: Boolean,
val timeSlotB: Calendar,
val timeSlotB: LocalDateTime,
val onRowClick: (NotificationProfile) -> Unit,
val onTimeSlotAClick: (NotificationProfile) -> Unit,
val onTimeSlotBClick: (NotificationProfile, Calendar) -> Unit,
val onTimeSlotBClick: (NotificationProfile, LocalDateTime) -> Unit,
val onViewSettingsClick: (NotificationProfile) -> Unit,
val onToggleClick: (NotificationProfile) -> Unit
) : PreferenceModel<Entry>() {
@@ -87,7 +87,7 @@ object NotificationProfileSelection {
expansion.visible = model.isExpanded
timeSlotB.text = context.getString(
R.string.NotificationProfileSelection__until_s,
DateUtils.getTimeString(context, Locale.getDefault(), model.timeSlotB.timeInMillis)
LocalTime.from(model.timeSlotB).formatHours()
)
if (TOGGLE_EXPANSION in payload || UPDATE_TIMESLOT in payload) {
@@ -107,7 +107,7 @@ object NotificationProfileSelection {
timeSlotB.text = context.getString(
R.string.NotificationProfileSelection__until_s,
DateUtils.getTimeString(context, Locale.getDefault(), model.timeSlotB.timeInMillis)
LocalTime.from(model.timeSlotB).formatHours()
)
itemView.isSelected = model.isOn

View File

@@ -35,7 +35,7 @@ import java.time.format.DateTimeFormatter
*/
class EditNotificationProfileScheduleFragment : LoggingFragment(R.layout.fragment_edit_notification_profile_schedule) {
private val viewModel: EditNotificationProfileScheduleViewModel by viewModels(factoryProducer = { EditNotificationProfileScheduleViewModel.Factory(profileId) })
private val viewModel: EditNotificationProfileScheduleViewModel by viewModels(factoryProducer = { EditNotificationProfileScheduleViewModel.Factory(profileId, createMode) })
private val lifecycleDisposable = LifecycleDisposable()
private val profileId: Long by lazy { EditNotificationProfileScheduleFragmentArgs.fromBundle(requireArguments()).profileId }

View File

@@ -17,7 +17,8 @@ import java.time.DayOfWeek
* from the database and into the [scheduleSubject] allowing the safe use of !! with [schedule].
*/
class EditNotificationProfileScheduleViewModel(
profileId: Long,
private val profileId: Long,
private val createMode: Boolean,
private val repository: NotificationProfilesRepository
) : ViewModel() {
@@ -72,14 +73,23 @@ class EditNotificationProfileScheduleViewModel(
} else if (createMode && !schedule.enabled) {
Single.just(SaveScheduleResult.Success)
} else {
repository.updateSchedule(schedule).toSingle { SaveScheduleResult.Success }
repository.updateSchedule(schedule)
.toSingleDefault(SaveScheduleResult.Success)
.flatMap { r ->
if (createMode && schedule.enabled && schedule.coversTime(System.currentTimeMillis())) {
repository.manuallyToggleProfile(profileId, schedule)
.toSingleDefault(r)
} else {
Single.just(r)
}
}
}
return result.observeOn(AndroidSchedulers.mainThread())
}
class Factory(private val profileId: Long) : ViewModelProvider.Factory {
class Factory(private val profileId: Long, private val createMode: Boolean) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return modelClass.cast(EditNotificationProfileScheduleViewModel(profileId, NotificationProfilesRepository()))!!
return modelClass.cast(EditNotificationProfileScheduleViewModel(profileId, createMode, NotificationProfilesRepository()))!!
}
}

View File

@@ -96,19 +96,23 @@ class NotificationProfilesRepository {
}
fun manuallyToggleProfile(profile: NotificationProfile, now: Long = System.currentTimeMillis()): Completable {
return manuallyToggleProfile(profile.id, profile.schedule, now)
}
fun manuallyToggleProfile(profileId: Long, schedule: NotificationProfileSchedule, now: Long = System.currentTimeMillis()): Completable {
return Completable.fromAction {
val profiles = database.getProfiles()
val activeProfile = NotificationProfiles.getActiveProfile(profiles, now)
if (profile.id == activeProfile?.id) {
if (profileId == activeProfile?.id) {
SignalStore.notificationProfileValues().manuallyEnabledProfile = 0
SignalStore.notificationProfileValues().manuallyEnabledUntil = 0
SignalStore.notificationProfileValues().manuallyDisabledAt = now
SignalStore.notificationProfileValues().lastProfilePopup = 0
SignalStore.notificationProfileValues().lastProfilePopupTime = 0
} else {
val inScheduledWindow = profile.schedule.isCurrentlyActive(now)
SignalStore.notificationProfileValues().manuallyEnabledProfile = if (inScheduledWindow) 0 else profile.id
val inScheduledWindow = schedule.isCurrentlyActive(now)
SignalStore.notificationProfileValues().manuallyEnabledProfile = if (inScheduledWindow) 0 else profileId
SignalStore.notificationProfileValues().manuallyEnabledUntil = if (inScheduledWindow) 0 else Long.MAX_VALUE
SignalStore.notificationProfileValues().manuallyDisabledAt = if (inScheduledWindow) 0 else now
}

View File

@@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.util.formatHours
import org.thoughtcrime.securesms.util.toLocalDateTime
import org.thoughtcrime.securesms.util.toLocalTime
import org.thoughtcrime.securesms.util.toMillis
import org.thoughtcrime.securesms.util.toOffset
import java.time.LocalDateTime
import java.time.ZoneId
@@ -26,7 +27,7 @@ object NotificationProfiles {
val manualProfile: NotificationProfile? = profiles.firstOrNull { it.id == storeValues.manuallyEnabledProfile }
val scheduledProfile: NotificationProfile? = profiles.sortedDescending().filter { it.schedule.isCurrentlyActive(now, zoneId) }.firstOrNull { profile ->
profile.schedule.startDateTime(localNow).toMillis() > storeValues.manuallyDisabledAt
profile.schedule.startDateTime(localNow).toMillis(zoneId.toOffset()) > storeValues.manuallyDisabledAt
}
if (manualProfile == null || scheduledProfile == null) {

View File

@@ -3,17 +3,25 @@ package org.thoughtcrime.securesms.util
import java.time.Instant
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.OffsetDateTime
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.concurrent.TimeUnit
/**
* Given a [ZoneId] return the time offset as a [ZoneOffset].
*/
fun ZoneId.toOffset(): ZoneOffset {
return OffsetDateTime.now(this).offset
}
/**
* Convert [LocalDateTime] to be same as [System.currentTimeMillis]
*/
fun LocalDateTime.toMillis(): Long {
return TimeUnit.SECONDS.toMillis(toEpochSecond(ZoneOffset.UTC))
fun LocalDateTime.toMillis(zoneOffset: ZoneOffset = ZoneId.systemDefault().toOffset()): Long {
return TimeUnit.SECONDS.toMillis(toEpochSecond(zoneOffset))
}
/**