Fixed some bugs with disappearing messages

Fixed an issue where the DisappearingMessages job could incorrectly overwrite it's nextRunTimestamp
Fixed an issue where sent/self-send messages wouldn't correctly trigger the disappearing messages job
Fixed an issue where sending the mnemonic along with an attachment wasn't showing the warning prompt
Fixed an issue where the home screen wasn't updating on launch if the displayed messages were removed disappearing messages
Fixed a small UI glitch where the input field wouldn't immediately get cleared when sending a message (unfortunately there is a slight delay before the message appears still)
pull/693/head
Morgan Pretty 3 years ago
parent 81d4497ed1
commit 7dc75af361

@ -315,12 +315,11 @@ extension ConversationVC:
let modal = SendSeedModal() let modal = SendSeedModal()
modal.modalPresentationStyle = .overFullScreen modal.modalPresentationStyle = .overFullScreen
modal.modalTransitionStyle = .crossDissolve modal.modalTransitionStyle = .crossDissolve
modal.proceed = { self.sendMessage(hasPermissionToSendSeed: true) } modal.proceed = { [weak self] in self?.sendMessage(hasPermissionToSendSeed: true) }
return present(modal, animated: true, completion: nil) return present(modal, animated: true, completion: nil)
} }
// Clearing this out immediately (even though it already happens in 'messageSent') to prevent // Clearing this out immediately to make this appear more snappy
// "double sending" if the user rapidly taps the send button
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
self?.snInputView.text = "" self?.snInputView.text = ""
self?.snInputView.quoteDraftInfo = nil self?.snInputView.quoteDraftInfo = nil
@ -416,7 +415,7 @@ extension ConversationVC:
) )
} }
func sendAttachments(_ attachments: [SignalAttachment], with text: String, onComplete: (() -> ())? = nil) { func sendAttachments(_ attachments: [SignalAttachment], with text: String, hasPermissionToSendSeed: Bool = false, onComplete: (() -> ())? = nil) {
guard !showBlockedModalIfNeeded() else { return } guard !showBlockedModalIfNeeded() else { return }
for attachment in attachments { for attachment in attachments {
@ -427,6 +426,25 @@ extension ConversationVC:
let text = replaceMentions(in: snInputView.text.trimmingCharacters(in: .whitespacesAndNewlines)) let text = replaceMentions(in: snInputView.text.trimmingCharacters(in: .whitespacesAndNewlines))
if text.contains(mnemonic) && !viewModel.threadData.threadIsNoteToSelf && !hasPermissionToSendSeed {
// Warn the user if they're about to send their seed to someone
let modal = SendSeedModal()
modal.modalPresentationStyle = .overFullScreen
modal.modalTransitionStyle = .crossDissolve
modal.proceed = { [weak self] in
self?.sendAttachments(attachments, with: text, hasPermissionToSendSeed: true, onComplete: onComplete)
}
return present(modal, animated: true, completion: nil)
}
// Clearing this out immediately to make this appear more snappy
DispatchQueue.main.async { [weak self] in
self?.snInputView.text = ""
self?.snInputView.quoteDraftInfo = nil
self?.resetMentions()
}
// Note: 'shouldBeVisible' is set to true the first time a thread is saved so we can // Note: 'shouldBeVisible' is set to true the first time a thread is saved so we can
// use it to determine if the user is creating a new thread and update the 'isApproved' // use it to determine if the user is creating a new thread and update the 'isApproved'
// flags appropriately // flags appropriately
@ -492,13 +510,6 @@ extension ConversationVC:
} }
func handleMessageSent() { func handleMessageSent() {
DispatchQueue.main.async { [weak self] in
self?.snInputView.text = ""
self?.snInputView.quoteDraftInfo = nil
self?.resetMentions()
}
if Storage.shared[.playNotificationSoundInForeground] { if Storage.shared[.playNotificationSoundInForeground] {
let soundID = Preferences.Sound.systemSoundId(for: .messageSent, quiet: true) let soundID = Preferences.Sound.systemSoundId(for: .messageSent, quiet: true)
AudioServicesPlaySystemSound(soundID) AudioServicesPlaySystemSound(soundID)

@ -892,7 +892,7 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
deleteSectionsAnimation: .none, deleteSectionsAnimation: .none,
insertSectionsAnimation: .none, insertSectionsAnimation: .none,
reloadSectionsAnimation: .none, reloadSectionsAnimation: .none,
deleteRowsAnimation: .bottom, deleteRowsAnimation: .fade,
insertRowsAnimation: .none, insertRowsAnimation: .none,
reloadRowsAnimation: .none, reloadRowsAnimation: .none,
interrupt: { itemChangeInfo?.isInsertAtTop == true || $0.changeCount > ConversationViewModel.pageSize } interrupt: { itemChangeInfo?.isInsertAtTop == true || $0.changeCount > ConversationViewModel.pageSize }

@ -250,16 +250,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
Configuration.performMainSetup() Configuration.performMainSetup()
JobRunner.add(executor: SyncPushTokensJob.self, for: .syncPushTokens) JobRunner.add(executor: SyncPushTokensJob.self, for: .syncPushTokens)
// Trigger any launch-specific jobs and start the JobRunner
JobRunner.appDidFinishLaunching()
/// Setup the UI /// Setup the UI
/// ///
/// **Note:** This **MUST** be run before calling `AppReadiness.setAppIsReady()` otherwise if /// **Note:** This **MUST** be run before calling:
/// we are launching the app from a push notification the HomeVC won't be setup yet and it won't open the /// - `AppReadiness.setAppIsReady()`:
/// related thread /// If we are launching the app from a push notification the HomeVC won't be setup yet
/// and it won't open the related thread
///
/// - `JobRunner.appDidFinishLaunching()`:
/// The jobs which run on launch (eg. DisappearingMessages job) can impact the interactions
/// which get fetched to display on the home screen, if the PagedDatabaseObserver hasn't
/// been setup yet then the home screen can show stale (ie. deleted) interactions incorrectly
self.ensureRootViewController(isPreAppReadyCall: true) self.ensureRootViewController(isPreAppReadyCall: true)
// Trigger any launch-specific jobs and start the JobRunner
JobRunner.appDidFinishLaunching()
// Note that this does much more than set a flag; // Note that this does much more than set a flag;
// it will also run all deferred blocks (including the JobRunner // it will also run all deferred blocks (including the JobRunner
// 'appDidBecomeActive' method) // 'appDidBecomeActive' method)

@ -523,8 +523,13 @@ public extension Interaction {
.asRequest(of: Int64.self) .asRequest(of: Int64.self)
.fetchAll(db) .fetchAll(db)
// Don't bother continuing if there are not interactions to mark as read // If there are no other interactions to mark as read then just schedule the jobs
guard !interactionIdsToMarkAsRead.isEmpty else { return } // for this interaction (need to ensure the disapeparing messages run for sync'ed
// outgoing messages which will always have 'wasRead' as false)
guard !interactionIdsToMarkAsRead.isEmpty else {
scheduleJobs(interactionIds: [interactionId])
return
}
// Update the `wasRead` flag to true // Update the `wasRead` flag to true
try interactionQuery.updateAll(db, Columns.wasRead.set(to: true)) try interactionQuery.updateAll(db, Columns.wasRead.set(to: true))

@ -17,7 +17,7 @@ public enum DisappearingMessagesJob: JobExecutor {
deferred: @escaping (Job) -> () deferred: @escaping (Job) -> ()
) { ) {
// The 'backgroundTask' gets captured and cleared within the 'completion' block // The 'backgroundTask' gets captured and cleared within the 'completion' block
let timestampNowMs: TimeInterval = (Date().timeIntervalSince1970 * 1000) let timestampNowMs: TimeInterval = ceil(Date().timeIntervalSince1970 * 1000)
var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: #function) var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: #function)
let updatedJob: Job? = Storage.shared.write { db in let updatedJob: Job? = Storage.shared.write { db in
@ -29,12 +29,9 @@ public enum DisappearingMessagesJob: JobExecutor {
// Update the next run timestamp for the DisappearingMessagesJob (if the call // Update the next run timestamp for the DisappearingMessagesJob (if the call
// to 'updateNextRunIfNeeded' returns 'nil' then it doesn't need to re-run so // to 'updateNextRunIfNeeded' returns 'nil' then it doesn't need to re-run so
// should have it's 'nextRunTimestamp' cleared) // should have it's 'nextRunTimestamp' cleared)
return updateNextRunIfNeeded(db) return try updateNextRunIfNeeded(db)
.defaulting( .defaulting(to: job.with(nextRunTimestamp: 0))
to: try job
.with(nextRunTimestamp: 0)
.saved(db) .saved(db)
)
} }
success(updatedJob ?? job, false) success(updatedJob ?? job, false)
@ -65,7 +62,7 @@ public extension DisappearingMessagesJob {
return try? Job return try? Job
.filter(Job.Columns.variant == Job.Variant.disappearingMessages) .filter(Job.Columns.variant == Job.Variant.disappearingMessages)
.fetchOne(db)? .fetchOne(db)?
.with(nextRunTimestamp: ((nextExpirationTimestampMs / 1000) + 1)) .with(nextRunTimestamp: ceil(nextExpirationTimestampMs / 1000))
.saved(db) .saved(db)
} }

@ -98,7 +98,7 @@ public class TypingIndicators {
withTimeInterval: (direction == .outgoing ? 3 : 5), withTimeInterval: (direction == .outgoing ? 3 : 5),
repeats: false repeats: false
) { _ in ) { _ in
Storage.shared.write { db in Storage.shared.writeAsync { db in
TypingIndicators.didStopTyping(db, threadId: threadId, direction: direction) TypingIndicators.didStopTyping(db, threadId: threadId, direction: direction)
} }
} }
@ -123,7 +123,7 @@ public class TypingIndicators {
withTimeInterval: 10, withTimeInterval: 10,
repeats: false repeats: false
) { [weak self] _ in ) { [weak self] _ in
Storage.shared.write { db in Storage.shared.writeAsync { db in
self?.scheduleRefreshCallback(db) self?.scheduleRefreshCallback(db)
} }
} }

@ -888,21 +888,28 @@ private final class JobQueue {
// but we want at least 1 second to pass before doing so - the job itself should // but we want at least 1 second to pass before doing so - the job itself should
// really update it's own 'nextRunTimestamp' (this is just a safety net) // really update it's own 'nextRunTimestamp' (this is just a safety net)
case .recurring where job.nextRunTimestamp <= Date().timeIntervalSince1970: case .recurring where job.nextRunTimestamp <= Date().timeIntervalSince1970:
guard let jobId: Int64 = job.id else { break }
Storage.shared.write { db in Storage.shared.write { db in
_ = try job _ = try Job
.with(nextRunTimestamp: (Date().timeIntervalSince1970 + 1)) .filter(id: jobId)
.saved(db) .updateAll(
db,
Job.Columns.failureCount.set(to: 0),
Job.Columns.nextRunTimestamp.set(to: (Date().timeIntervalSince1970 + 1))
)
} }
// For `recurringOnLaunch/Active` jobs which have already run, we want to clear their // For `recurringOnLaunch/Active` jobs which have already run but failed once, we need to
// `failureCount` and `nextRunTimestamp` to prevent them from endlessly running over // clear their `failureCount` and `nextRunTimestamp` to prevent them from endlessly running
// and over and reset their retry backoff in case they fail next time // over and over again
case .recurringOnLaunch, .recurringOnActive: case .recurringOnLaunch, .recurringOnActive:
if guard
let jobId: Int64 = job.id, let jobId: Int64 = job.id,
job.failureCount != 0 && job.failureCount != 0 &&
job.nextRunTimestamp > TimeInterval.leastNonzeroMagnitude job.nextRunTimestamp > TimeInterval.leastNonzeroMagnitude
{ else { break }
Storage.shared.write { db in Storage.shared.write { db in
_ = try Job _ = try Job
.filter(id: jobId) .filter(id: jobId)
@ -912,7 +919,6 @@ private final class JobQueue {
Job.Columns.nextRunTimestamp.set(to: 0) Job.Columns.nextRunTimestamp.set(to: 0)
) )
} }
}
default: break default: break
} }

Loading…
Cancel
Save