First draft of CLVBackupProgressView
This commit is contained in:
parent
b7ea9e5dcc
commit
696d33c750
@ -1770,7 +1770,7 @@
|
||||
B99CD9452DB066740035C77B /* AttachmentSaving.swift in Sources */ = {isa = PBXBuildFile; fileRef = D954A5AC2D7BA7DA00F61C36 /* AttachmentSaving.swift */; };
|
||||
B9A0807A2B07D76A000FDB5B /* HomeTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A080792B07D76A000FDB5B /* HomeTabViewController.swift */; };
|
||||
B9A47ACE2D36DA6B0024DD9C /* circular_indeterminate.json in Resources */ = {isa = PBXBuildFile; fileRef = B9A47ACD2D36DA6B0024DD9C /* circular_indeterminate.json */; };
|
||||
B9A53B912CF507FB0000578B /* BackupProgressModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A53B902CF507FB0000578B /* BackupProgressModal.swift */; };
|
||||
B9A53B912CF507FB0000578B /* BackupRestoreProgressModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A53B902CF507FB0000578B /* BackupRestoreProgressModal.swift */; };
|
||||
B9A53B932CF7928A0000578B /* SheetPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A53B922CF7928A0000578B /* SheetPreviewViewController.swift */; };
|
||||
B9A53B952CF799590000578B /* LinkOrSyncPickerSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A53B942CF799590000578B /* LinkOrSyncPickerSheet.swift */; };
|
||||
B9A53B992D0250FC0000578B /* EditCallLinkNameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A53B982D0250FC0000578B /* EditCallLinkNameViewController.swift */; };
|
||||
@ -2685,6 +2685,7 @@
|
||||
D953427B2D8B71190087AD93 /* recipient_self_08.binproto in Resources */ = {isa = PBXBuildFile; fileRef = D95342422D8B71190087AD93 /* recipient_self_08.binproto */; };
|
||||
D953427C2D8B71190087AD93 /* recipient_self_08.txtproto in Resources */ = {isa = PBXBuildFile; fileRef = D95342432D8B71190087AD93 /* recipient_self_08.txtproto */; };
|
||||
D953427D2D8B71190087AD93 /* recipient_groups_11.txtproto in Resources */ = {isa = PBXBuildFile; fileRef = D95342312D8B71190087AD93 /* recipient_groups_11.txtproto */; };
|
||||
D9538E412F3FF7FC002C887E /* UITableView+RowHeights.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9538E402F3FF7F3002C887E /* UITableView+RowHeights.swift */; };
|
||||
D954A5AE2D7BA9B200F61C36 /* AttachmentSharing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 762EBBCF2A2FB759002FD28F /* AttachmentSharing.swift */; };
|
||||
D95787772C6D2A080051AC74 /* TSInfoMessage+GroupUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D95787762C6D2A080051AC74 /* TSInfoMessage+GroupUpdates.swift */; };
|
||||
D95787792C6D2ADE0051AC74 /* TSInfoMessage+Payments.swift in Sources */ = {isa = PBXBuildFile; fileRef = D95787782C6D2ADE0051AC74 /* TSInfoMessage+Payments.swift */; };
|
||||
@ -3086,6 +3087,8 @@
|
||||
D9CAFAE62A538CA200B32BDE /* UsernameLinkQRCodeContentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9CAFAE52A538CA200B32BDE /* UsernameLinkQRCodeContentController.swift */; };
|
||||
D9CAFAEA2A53CB1F00B32BDE /* UsernameLinkTooltipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9CAFAE92A53CB1F00B32BDE /* UsernameLinkTooltipView.swift */; };
|
||||
D9CD40622A155C4800545803 /* TSInfoMessage+PersistableGroupUpdateItemTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9CD40612A155C4800545803 /* TSInfoMessage+PersistableGroupUpdateItemTest.swift */; };
|
||||
D9CFB0102F1FF87F00DFB14A /* ChatListViewController+BackupProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9CFB00F2F1FF87800DFB14A /* ChatListViewController+BackupProgressView.swift */; };
|
||||
D9CFB0142F20466300DFB14A /* ArcView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9CFB0132F20466100DFB14A /* ArcView.swift */; };
|
||||
D9D1A6EB2DD69D0800050A85 /* DonationReceiptCredentialRedemptionJobFinderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9D1A6EA2DD69CFE00050A85 /* DonationReceiptCredentialRedemptionJobFinderTest.swift */; };
|
||||
D9D3216A2A8AC9B0004FC110 /* OutgoingGroupCallUpdateMessageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9D321692A8AC9B0004FC110 /* OutgoingGroupCallUpdateMessageTest.swift */; };
|
||||
D9D5018A2F2B16820068EEA5 /* KeyTransparencyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9D501892F2B16820068EEA5 /* KeyTransparencyManager.swift */; };
|
||||
@ -5933,7 +5936,7 @@
|
||||
B99CD9432DA9AB530035C77B /* TypedItemProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedItemProvider.swift; sourceTree = "<group>"; };
|
||||
B9A080792B07D76A000FDB5B /* HomeTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTabViewController.swift; sourceTree = "<group>"; };
|
||||
B9A47ACD2D36DA6B0024DD9C /* circular_indeterminate.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = circular_indeterminate.json; sourceTree = "<group>"; };
|
||||
B9A53B902CF507FB0000578B /* BackupProgressModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupProgressModal.swift; sourceTree = "<group>"; };
|
||||
B9A53B902CF507FB0000578B /* BackupRestoreProgressModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupRestoreProgressModal.swift; sourceTree = "<group>"; };
|
||||
B9A53B922CF7928A0000578B /* SheetPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetPreviewViewController.swift; sourceTree = "<group>"; };
|
||||
B9A53B942CF799590000578B /* LinkOrSyncPickerSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkOrSyncPickerSheet.swift; sourceTree = "<group>"; };
|
||||
B9A53B982D0250FC0000578B /* EditCallLinkNameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCallLinkNameViewController.swift; sourceTree = "<group>"; };
|
||||
@ -6855,6 +6858,7 @@
|
||||
D95342472D8B71190087AD93 /* recipient_self_10.txtproto */ = {isa = PBXFileReference; lastKnownFileType = text; path = recipient_self_10.txtproto; sourceTree = "<group>"; };
|
||||
D95342482D8B71190087AD93 /* recipient_self_11.binproto */ = {isa = PBXFileReference; lastKnownFileType = file; path = recipient_self_11.binproto; sourceTree = "<group>"; };
|
||||
D95342492D8B71190087AD93 /* recipient_self_11.txtproto */ = {isa = PBXFileReference; lastKnownFileType = text; path = recipient_self_11.txtproto; sourceTree = "<group>"; };
|
||||
D9538E402F3FF7F3002C887E /* UITableView+RowHeights.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+RowHeights.swift"; sourceTree = "<group>"; };
|
||||
D954A5AC2D7BA7DA00F61C36 /* AttachmentSaving.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentSaving.swift; sourceTree = "<group>"; };
|
||||
D95777B92B46411300CFE3AE /* GroupCallPeekClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupCallPeekClient.swift; sourceTree = "<group>"; };
|
||||
D95787762C6D2A080051AC74 /* TSInfoMessage+GroupUpdates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSInfoMessage+GroupUpdates.swift"; sourceTree = "<group>"; };
|
||||
@ -7263,6 +7267,8 @@
|
||||
D9CAFAE52A538CA200B32BDE /* UsernameLinkQRCodeContentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsernameLinkQRCodeContentController.swift; sourceTree = "<group>"; };
|
||||
D9CAFAE92A53CB1F00B32BDE /* UsernameLinkTooltipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsernameLinkTooltipView.swift; sourceTree = "<group>"; };
|
||||
D9CD40612A155C4800545803 /* TSInfoMessage+PersistableGroupUpdateItemTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSInfoMessage+PersistableGroupUpdateItemTest.swift"; sourceTree = "<group>"; };
|
||||
D9CFB00F2F1FF87800DFB14A /* ChatListViewController+BackupProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatListViewController+BackupProgressView.swift"; sourceTree = "<group>"; };
|
||||
D9CFB0132F20466100DFB14A /* ArcView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArcView.swift; sourceTree = "<group>"; };
|
||||
D9D1A6EA2DD69CFE00050A85 /* DonationReceiptCredentialRedemptionJobFinderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationReceiptCredentialRedemptionJobFinderTest.swift; sourceTree = "<group>"; };
|
||||
D9D321692A8AC9B0004FC110 /* OutgoingGroupCallUpdateMessageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingGroupCallUpdateMessageTest.swift; sourceTree = "<group>"; };
|
||||
D9D3217B2A8FEA9B004FC110 /* Groups.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = Groups.proto; sourceTree = "<group>"; };
|
||||
@ -8852,6 +8858,7 @@
|
||||
766CE0D92A32E52300AD609D /* UIStackView+SignalUI.swift */,
|
||||
F9C45D9329CB93E200B2CD2D /* UIStackView+SignalUITest.swift */,
|
||||
500FB6172915B86D00257951 /* UITableView+ReusableCell.swift */,
|
||||
D9538E402F3FF7F3002C887E /* UITableView+RowHeights.swift */,
|
||||
766CE0D72A32968600AD609D /* UIView+AutoLayout.swift */,
|
||||
3402A9E5271D97090084CBAE /* UIView+SignalUI.swift */,
|
||||
762A41682A37D71600057955 /* UIViewController+SignalUI.swift */,
|
||||
@ -10932,6 +10939,7 @@
|
||||
children = (
|
||||
B97802FF2DD65AFD00E9FC82 /* SwiftUI */,
|
||||
32E958A925C12B3800BF12AD /* AnimatedProgressView.swift */,
|
||||
D9CFB0132F20466100DFB14A /* ArcView.swift */,
|
||||
4C2F454E214C00E1004871FF /* AvatarTableViewCell.swift */,
|
||||
14E4A33F278EE999008408FD /* BlurredToolbarContainer.swift */,
|
||||
32A9E22524C11B3F00C43518 /* EmojiMoodPickerView.swift */,
|
||||
@ -11068,6 +11076,7 @@
|
||||
50101FB12B083C8100C648E4 /* ChatListSettingsButtonState.swift */,
|
||||
34E95C26269F6095004807EC /* ChatListViewController+Actions.swift */,
|
||||
66F98DE92DC5314E009F1A86 /* ChatListViewController+BackupDownloadProgressView.swift */,
|
||||
D9CFB00F2F1FF87800DFB14A /* ChatListViewController+BackupProgressView.swift */,
|
||||
76847C932A13416A008E2EAB /* ChatListViewController+Camera.swift */,
|
||||
34E95C28269F6109004807EC /* ChatListViewController+Helpers.swift */,
|
||||
34E95C2C26A0673F004807EC /* ChatListViewController+Loading.swift */,
|
||||
@ -11185,7 +11194,7 @@
|
||||
887B6DCD25F6C44800E677D4 /* Linked Devices */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B9A53B902CF507FB0000578B /* BackupProgressModal.swift */,
|
||||
B9A53B902CF507FB0000578B /* BackupRestoreProgressModal.swift */,
|
||||
505C2ED32997015800C23FB2 /* LinkDeviceViewController.swift */,
|
||||
B99287FA2CF0FE8D000D62C4 /* LinkedDevicesEducationSheet.swift */,
|
||||
B9E322E82CD191CF006DAF3B /* LinkedDevicesView.swift */,
|
||||
@ -17540,6 +17549,7 @@
|
||||
762A416B2A38397500057955 /* UIKit+Text.swift in Sources */,
|
||||
766CE0DA2A32E52300AD609D /* UIStackView+SignalUI.swift in Sources */,
|
||||
500FB6182915B86D00257951 /* UITableView+ReusableCell.swift in Sources */,
|
||||
D9538E412F3FF7FC002C887E /* UITableView+RowHeights.swift in Sources */,
|
||||
766CE0D82A32968600AD609D /* UIView+AutoLayout.swift in Sources */,
|
||||
3402A9E8271D97090084CBAE /* UIView+SignalUI.swift in Sources */,
|
||||
764FE03F2A2EC2E2004D2804 /* UIViewController+Permissions.swift in Sources */,
|
||||
@ -17617,6 +17627,7 @@
|
||||
F9BC0A2527FB8E730085B23D /* AppSettingsViewsUtil.swift in Sources */,
|
||||
1489ED0227A3D70200C7043A /* ArchivedConversationsCell.swift in Sources */,
|
||||
C1661A1A2C3D939300AB887F /* ArchivedPaymentHistoryItem.swift in Sources */,
|
||||
D9CFB0142F20466300DFB14A /* ArcView.swift in Sources */,
|
||||
F9B3A92D293553930071EB95 /* ASWebAuthenticationSession+Util.swift in Sources */,
|
||||
661AEE482C2088FD0046B1D8 /* AttachmentDownloadRetryRunner.swift in Sources */,
|
||||
88A9729222FA5D4B004B4FBF /* AttachmentFormatPickerView.swift in Sources */,
|
||||
@ -17653,10 +17664,10 @@
|
||||
D999345A2DE97BBC002C9196 /* BackupOnboardingCoordinator.swift in Sources */,
|
||||
D9DE34FD2DEE7765005099D7 /* BackupOnboardingIntroViewController.swift in Sources */,
|
||||
D98CA2AD2DF14A890060370E /* BackupOnboardingKeyIntroViewController.swift in Sources */,
|
||||
B9A53B912CF507FB0000578B /* BackupProgressModal.swift in Sources */,
|
||||
D98CA2B32DF245140060370E /* BackupRecordKeyViewController.swift in Sources */,
|
||||
04E66D422DFF3A4B0059DBAC /* BackupRecoveryKeyReminderCoordinator.swift in Sources */,
|
||||
50438A8E2ECBBDF600FCB28F /* BackupRefreshManager.swift in Sources */,
|
||||
B9A53B912CF507FB0000578B /* BackupRestoreProgressModal.swift in Sources */,
|
||||
0480F0002E57C51A006CBB29 /* BackupsEnabledNotificationMegaphone.swift in Sources */,
|
||||
D951F5312D9B236700C5EBF3 /* BackupSettingsViewController.swift in Sources */,
|
||||
D92CB5562F030F8300537EBE /* BackupSubscriptionAlreadyRedeemedSheet.swift in Sources */,
|
||||
@ -17750,6 +17761,7 @@
|
||||
50101FB22B083C8100C648E4 /* ChatListSettingsButtonState.swift in Sources */,
|
||||
34E95C27269F6096004807EC /* ChatListViewController+Actions.swift in Sources */,
|
||||
66F98DEA2DC53155009F1A86 /* ChatListViewController+BackupDownloadProgressView.swift in Sources */,
|
||||
D9CFB0102F1FF87F00DFB14A /* ChatListViewController+BackupProgressView.swift in Sources */,
|
||||
76847C942A13416A008E2EAB /* ChatListViewController+Camera.swift in Sources */,
|
||||
34E95C29269F6109004807EC /* ChatListViewController+Helpers.swift in Sources */,
|
||||
34E95C2D26A06740004807EC /* ChatListViewController+Loading.swift in Sources */,
|
||||
|
||||
@ -223,7 +223,7 @@ class BackupSettingsViewController:
|
||||
Task {
|
||||
await deviceSleepManager.manageBlockForUpdateStream(
|
||||
backupExportJobRunner.updates(),
|
||||
label: "BackupExportJob",
|
||||
label: "BackupSettings.BackupExportJob",
|
||||
) { [weak self] exportJobUpdate in
|
||||
guard let self else { return false }
|
||||
|
||||
@ -258,7 +258,7 @@ class BackupSettingsViewController:
|
||||
Task {
|
||||
await deviceSleepManager.manageBlockForUpdateStream(
|
||||
backupAttachmentDownloadTracker.updates(),
|
||||
label: "Downloads",
|
||||
label: "BackupSettings.BackupDownloads",
|
||||
) { [weak self] downloadTrackerUpdate in
|
||||
guard let self else { return false }
|
||||
|
||||
@ -295,7 +295,7 @@ class BackupSettingsViewController:
|
||||
Task {
|
||||
await deviceSleepManager.manageBlockForUpdateStream(
|
||||
backupAttachmentUploadTracker.updates(),
|
||||
label: "Uploads",
|
||||
label: "BackupSettings.BackupUploads",
|
||||
) { [weak self] uploadUpdate in
|
||||
guard let self else { return false }
|
||||
viewModel.latestBackupAttachmentUploadUpdate = uploadUpdate
|
||||
|
||||
@ -11,7 +11,7 @@ class RegistrationLoadingViewController: OWSViewController, OWSNavigationChildCo
|
||||
case generic
|
||||
case submittingPhoneNumber(e164: String)
|
||||
case submittingVerificationCode
|
||||
case restoringBackup(BackupProgressModal)
|
||||
case restoringBackup(BackupRestoreProgressModal)
|
||||
}
|
||||
|
||||
init(mode: RegistrationLoadingMode) {
|
||||
|
||||
@ -92,7 +92,7 @@ public class RegistrationNavigationController: OWSNavigationController {
|
||||
Task { @MainActor [self] in
|
||||
let step = await step.awaitable()
|
||||
|
||||
if let progressModal = self.presentedViewController as? BackupProgressModal {
|
||||
if let progressModal = self.presentedViewController as? BackupRestoreProgressModal {
|
||||
Logger.info("Dismissing progress view")
|
||||
await progressModal.completeAndDismiss()
|
||||
}
|
||||
@ -737,7 +737,7 @@ extension RegistrationNavigationController: RegistrationRestoreFromBackupConfirm
|
||||
|
||||
func restoreFromBackupConfirmed() {
|
||||
Task { @MainActor in
|
||||
let progressModal = BackupProgressModal(style: .backupRestore)
|
||||
let progressModal = BackupRestoreProgressModal(style: .backupRestore)
|
||||
let (progress, stream) = await OWSSequentialProgress<BackupRestoreProgressPhase>.createSink()
|
||||
Task { @MainActor in
|
||||
for await progress in stream {
|
||||
|
||||
@ -10,7 +10,7 @@ import SwiftUI
|
||||
|
||||
// MARK: View Model
|
||||
|
||||
class BackupProgressViewModel: ObservableObject {
|
||||
class BackupRestoreProgressViewModel: ObservableObject {
|
||||
|
||||
@Published var didTapCancel: Bool = false
|
||||
@Published var taskProgress: Float = 0
|
||||
@ -128,19 +128,19 @@ class BackupProgressViewModel: ObservableObject {
|
||||
|
||||
// MARK: Hosting Controller
|
||||
|
||||
class BackupProgressModal: HostingController<BackupProgressView>, LinkAndSyncProgressUI {
|
||||
class BackupRestoreProgressModal: HostingController<BackupRestoreProgressView>, LinkAndSyncProgressUI {
|
||||
|
||||
var shouldSuppressNotifications: Bool { true }
|
||||
|
||||
let viewModel = BackupProgressViewModel()
|
||||
let viewModel = BackupRestoreProgressViewModel()
|
||||
|
||||
var backupTask: Task<Void, Never>? {
|
||||
get { viewModel.backupTask }
|
||||
set { viewModel.backupTask = newValue }
|
||||
}
|
||||
|
||||
init(style: BackupProgressView.Style) {
|
||||
super.init(wrappedView: BackupProgressView(
|
||||
init(style: BackupRestoreProgressView.Style) {
|
||||
super.init(wrappedView: BackupRestoreProgressView(
|
||||
style: style,
|
||||
viewModel: viewModel,
|
||||
))
|
||||
@ -191,7 +191,7 @@ class BackupProgressModal: HostingController<BackupProgressView>, LinkAndSyncPro
|
||||
|
||||
// MARK: SwiftUI View
|
||||
|
||||
struct BackupProgressView: View {
|
||||
struct BackupRestoreProgressView: View {
|
||||
@Environment(\.appearanceTransitionState) private var appearanceTransitionState
|
||||
|
||||
enum Style {
|
||||
@ -200,7 +200,7 @@ struct BackupProgressView: View {
|
||||
}
|
||||
|
||||
fileprivate var style: Style
|
||||
@ObservedObject fileprivate var viewModel: BackupProgressViewModel
|
||||
@ObservedObject fileprivate var viewModel: BackupRestoreProgressViewModel
|
||||
|
||||
@State private var indeterminateProgressIsPlaying = false
|
||||
private var loopMode: LottieLoopMode {
|
||||
@ -430,7 +430,7 @@ func simulateProgress(for source: OWSProgressSource) async throws {
|
||||
@MainActor
|
||||
@available(iOS 17, *)
|
||||
private func setupDemoProgressBackupRestore(
|
||||
modal: BackupProgressModal,
|
||||
modal: BackupRestoreProgressModal,
|
||||
instantComplete: Bool,
|
||||
) async throws {
|
||||
let progress = await OWSSequentialProgress<BackupRestoreProgressPhase>.createSink { progress in
|
||||
@ -475,7 +475,7 @@ private func setupDemoProgressBackupRestore(
|
||||
@MainActor
|
||||
@available(iOS 17, *)
|
||||
private func setupDemoProgress(
|
||||
modal: BackupProgressModal,
|
||||
modal: BackupRestoreProgressModal,
|
||||
slowLinking: Bool,
|
||||
) async throws {
|
||||
let progress = await OWSSequentialProgress<PrimaryLinkNSyncProgressPhase>.createSink { progress in
|
||||
@ -527,7 +527,7 @@ private func setupDemoProgress(
|
||||
@MainActor
|
||||
@available(iOS 17, *)
|
||||
func demoTask(
|
||||
modal: BackupProgressModal,
|
||||
modal: BackupRestoreProgressModal,
|
||||
slowLinking: Bool,
|
||||
) -> Task<Void, Never> {
|
||||
Task {
|
||||
@ -548,7 +548,7 @@ func demoTask(
|
||||
@available(iOS 17, *)
|
||||
#Preview("Slow linking") {
|
||||
SheetPreviewViewController(animateFirstAppearance: true) {
|
||||
let modal = BackupProgressModal(style: .linkAndSync)
|
||||
let modal = BackupRestoreProgressModal(style: .linkAndSync)
|
||||
modal.backupTask = demoTask(modal: modal, slowLinking: true)
|
||||
return modal
|
||||
}
|
||||
@ -557,7 +557,7 @@ func demoTask(
|
||||
@available(iOS 17, *)
|
||||
#Preview("Fast linking") {
|
||||
SheetPreviewViewController(animateFirstAppearance: true) {
|
||||
let modal = BackupProgressModal(style: .linkAndSync)
|
||||
let modal = BackupRestoreProgressModal(style: .linkAndSync)
|
||||
modal.backupTask = demoTask(modal: modal, slowLinking: false)
|
||||
return modal
|
||||
}
|
||||
@ -566,7 +566,7 @@ func demoTask(
|
||||
@available(iOS 17, *)
|
||||
#Preview("Backup restore") {
|
||||
SheetPreviewViewController(animateFirstAppearance: true) {
|
||||
let modal = BackupProgressModal(style: .backupRestore)
|
||||
let modal = BackupRestoreProgressModal(style: .backupRestore)
|
||||
modal.backupTask = Task {
|
||||
try? await setupDemoProgressBackupRestore(modal: modal, instantComplete: false)
|
||||
}
|
||||
@ -577,7 +577,7 @@ func demoTask(
|
||||
@available(iOS 17, *)
|
||||
#Preview("Backup restore - instant complete") {
|
||||
SheetPreviewViewController(animateFirstAppearance: true) {
|
||||
let modal = BackupProgressModal(style: .backupRestore)
|
||||
let modal = BackupRestoreProgressModal(style: .backupRestore)
|
||||
modal.backupTask = Task {
|
||||
try? await setupDemoProgressBackupRestore(modal: modal, instantComplete: true)
|
||||
}
|
||||
@ -267,7 +267,7 @@ extension LinkedDevicesViewModel: LinkDeviceViewControllerDelegate {
|
||||
}
|
||||
|
||||
// Don't wait for the view pop to start the linking process
|
||||
let linkAndSyncProgressModal = BackupProgressModal(style: .linkAndSync)
|
||||
let linkAndSyncProgressModal = BackupRestoreProgressModal(style: .linkAndSync)
|
||||
linkDeviceViewController.popToLinkedDeviceList { [weak self] in
|
||||
self?.present.send(.activityIndicator(linkAndSyncProgressModal))
|
||||
}
|
||||
|
||||
@ -76,15 +76,19 @@ struct CLVRenderState {
|
||||
|
||||
case .reminders where hasVisibleReminders,
|
||||
.backupDownloadProgressView where shouldBackupDownloadProgressViewBeVisible,
|
||||
.backupProgressView where shouldBackupProgressViewBeVisible,
|
||||
.archiveButton where hasArchivedThreadsRow:
|
||||
return Section(type: sectionType)
|
||||
|
||||
case .reminders,
|
||||
.backupDownloadProgressView,
|
||||
.backupProgressView,
|
||||
.archiveButton:
|
||||
return nil
|
||||
|
||||
case .inboxFilterFooter:
|
||||
guard let inboxFilterSection else { return nil }
|
||||
return Section(type: sectionType, value: inboxFilterSection)
|
||||
|
||||
case .reminders, .backupDownloadProgressView, .archiveButton:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,11 +112,19 @@ struct CLVRenderState {
|
||||
viewInfo.shouldBackupDownloadProgressViewBeVisible
|
||||
}
|
||||
|
||||
var shouldBackupProgressViewBeVisible: Bool {
|
||||
viewInfo.shouldBackupProgressViewBeVisible
|
||||
}
|
||||
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
func numberOfRows(in section: Section) -> Int {
|
||||
switch section.type {
|
||||
case .reminders, .backupDownloadProgressView, .archiveButton, .inboxFilterFooter:
|
||||
case .reminders,
|
||||
.backupDownloadProgressView,
|
||||
.backupProgressView,
|
||||
.archiveButton,
|
||||
.inboxFilterFooter:
|
||||
return 1
|
||||
case .pinned:
|
||||
return pinnedThreadUniqueIds.count
|
||||
@ -130,7 +142,12 @@ struct CLVRenderState {
|
||||
let oldValue = renderState.items(in: section) ?? []
|
||||
return items.difference(from: oldValue)
|
||||
|
||||
case .pinned, .unpinned, .reminders, .backupDownloadProgressView, .archiveButton:
|
||||
case .pinned,
|
||||
.unpinned,
|
||||
.reminders,
|
||||
.backupDownloadProgressView,
|
||||
.backupProgressView,
|
||||
.archiveButton:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -144,7 +161,12 @@ struct CLVRenderState {
|
||||
return nil
|
||||
}
|
||||
|
||||
case .pinned, .unpinned, .reminders, .backupDownloadProgressView, .archiveButton:
|
||||
case .pinned,
|
||||
.unpinned,
|
||||
.reminders,
|
||||
.backupDownloadProgressView,
|
||||
.backupProgressView,
|
||||
.archiveButton:
|
||||
owsFailDebug("Section diffing not yet supported in section '\(section.type)'")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ public enum ChatListMode: Int, CaseIterable {
|
||||
public enum ChatListSectionType: String, CaseIterable {
|
||||
case reminders
|
||||
case backupDownloadProgressView
|
||||
case backupProgressView
|
||||
case pinned
|
||||
case unpinned
|
||||
case archiveButton
|
||||
@ -185,7 +186,11 @@ class CLVTableDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||
}
|
||||
let conversationIndexPaths = visibleIndexPaths.compactMap { indexPath -> IndexPath? in
|
||||
switch renderState.sections[indexPath.section].type {
|
||||
case .reminders, .backupDownloadProgressView, .archiveButton, .inboxFilterFooter:
|
||||
case .reminders,
|
||||
.backupDownloadProgressView,
|
||||
.backupProgressView,
|
||||
.archiveButton,
|
||||
.inboxFilterFooter:
|
||||
return nil
|
||||
case .pinned, .unpinned:
|
||||
return indexPath
|
||||
@ -324,7 +329,7 @@ class CLVTableDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||
case .reminders, .inboxFilterFooter:
|
||||
return nil
|
||||
|
||||
case .backupDownloadProgressView, .archiveButton:
|
||||
case .backupDownloadProgressView, .backupProgressView, .archiveButton:
|
||||
return indexPath
|
||||
|
||||
case .pinned, .unpinned:
|
||||
@ -373,6 +378,10 @@ class CLVTableDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||
tableView.deselectRow(at: indexPath, animated: false)
|
||||
viewController.handleBackupDownloadProgressViewTapped()
|
||||
|
||||
case .backupProgressView:
|
||||
tableView.deselectRow(at: indexPath, animated: false)
|
||||
viewController.handleBackupProgressViewTapped()
|
||||
|
||||
case .pinned, .unpinned:
|
||||
guard let threadUniqueId = renderState.threadUniqueId(forIndexPath: indexPath) else {
|
||||
owsFailDebug("Missing thread.")
|
||||
@ -536,7 +545,7 @@ class CLVTableDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
switch renderState.sections[indexPath.section].type {
|
||||
case .reminders, .archiveButton, .inboxFilterFooter:
|
||||
case .reminders, .archiveButton, .inboxFilterFooter, .backupProgressView:
|
||||
return UITableView.automaticDimension
|
||||
case .backupDownloadProgressView:
|
||||
guard let viewState = viewController?.viewState else {
|
||||
@ -562,6 +571,8 @@ class CLVTableDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||
cell = viewController.viewState.reminderViews.reminderViewCell
|
||||
case .backupDownloadProgressView:
|
||||
cell = viewController.viewState.backupDownloadProgressView.backupDownloadProgressViewCell
|
||||
case .backupProgressView:
|
||||
cell = viewController.viewState.backupProgressView.backupProgressViewCell
|
||||
case .pinned, .unpinned:
|
||||
cell = buildConversationCell(tableView: tableView, indexPath: indexPath)
|
||||
case .archiveButton:
|
||||
@ -650,7 +661,11 @@ class CLVTableDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||
switch renderState.sections[indexPath.section].type {
|
||||
case .reminders, .backupDownloadProgressView, .archiveButton, .inboxFilterFooter:
|
||||
case .reminders,
|
||||
.backupDownloadProgressView,
|
||||
.backupProgressView,
|
||||
.archiveButton,
|
||||
.inboxFilterFooter:
|
||||
return nil
|
||||
|
||||
case .pinned, .unpinned:
|
||||
@ -675,7 +690,11 @@ class CLVTableDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
switch renderState.sections[indexPath.section].type {
|
||||
case .reminders, .backupDownloadProgressView, .archiveButton, .inboxFilterFooter:
|
||||
case .reminders,
|
||||
.backupDownloadProgressView,
|
||||
.backupProgressView,
|
||||
.archiveButton,
|
||||
.inboxFilterFooter:
|
||||
return false
|
||||
case .pinned, .unpinned:
|
||||
return true
|
||||
@ -684,7 +703,11 @@ class CLVTableDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||
switch renderState.sections[indexPath.section].type {
|
||||
case .reminders, .backupDownloadProgressView, .archiveButton, .inboxFilterFooter:
|
||||
case .reminders,
|
||||
.backupDownloadProgressView,
|
||||
.backupProgressView,
|
||||
.archiveButton,
|
||||
.inboxFilterFooter:
|
||||
return nil
|
||||
|
||||
case .pinned, .unpinned:
|
||||
|
||||
@ -14,6 +14,7 @@ struct CLVViewInfo: Equatable {
|
||||
let isMultiselectActive: Bool
|
||||
let hasVisibleReminders: Bool
|
||||
let shouldBackupDownloadProgressViewBeVisible: Bool
|
||||
let shouldBackupProgressViewBeVisible: Bool
|
||||
let lastSelectedThreadId: String?
|
||||
let requiredVisibleThreadIds: Set<String>
|
||||
|
||||
@ -30,6 +31,7 @@ struct CLVViewInfo: Equatable {
|
||||
isMultiselectActive: false,
|
||||
hasVisibleReminders: false,
|
||||
shouldBackupDownloadProgressViewBeVisible: false,
|
||||
shouldBackupProgressViewBeVisible: false,
|
||||
lastSelectedThreadId: nil,
|
||||
requiredVisibleThreadIds: [],
|
||||
)
|
||||
@ -42,6 +44,7 @@ struct CLVViewInfo: Equatable {
|
||||
lastSelectedThreadId: String?,
|
||||
hasVisibleReminders: Bool,
|
||||
shouldBackupDownloadProgressViewBeVisible: Bool,
|
||||
shouldBackupProgressViewBeVisible: Bool,
|
||||
transaction: DBReadTransaction,
|
||||
) -> CLVViewInfo {
|
||||
do {
|
||||
@ -61,6 +64,7 @@ struct CLVViewInfo: Equatable {
|
||||
isMultiselectActive: isMultiselectActive,
|
||||
hasVisibleReminders: hasVisibleReminders,
|
||||
shouldBackupDownloadProgressViewBeVisible: shouldBackupDownloadProgressViewBeVisible,
|
||||
shouldBackupProgressViewBeVisible: shouldBackupProgressViewBeVisible,
|
||||
lastSelectedThreadId: lastSelectedThreadId,
|
||||
requiredVisibleThreadIds: requiredThreadIds,
|
||||
)
|
||||
|
||||
@ -25,6 +25,7 @@ class CLVViewState {
|
||||
let containerView: ChatListContainerView
|
||||
let reminderViews: CLVReminderViews
|
||||
let backupDownloadProgressView: CLVBackupDownloadProgressView
|
||||
let backupProgressView: CLVBackupProgressView
|
||||
let settingsButtonCreator: ChatListSettingsButtonState
|
||||
let proxyButtonCreator: ChatListProxyButtonCreator
|
||||
|
||||
@ -131,6 +132,7 @@ class CLVViewState {
|
||||
self.containerView = ChatListContainerView(tableView: tableDataSource.tableView, searchBar: searchController.searchBar)
|
||||
self.reminderViews = CLVReminderViews()
|
||||
self.backupDownloadProgressView = CLVBackupDownloadProgressView()
|
||||
self.backupProgressView = CLVBackupProgressView()
|
||||
self.settingsButtonCreator = ChatListSettingsButtonState()
|
||||
self.proxyButtonCreator = ChatListProxyButtonCreator(chatConnectionManager: DependenciesBridge.shared.chatConnectionManager)
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ public class CLVBackupDownloadProgressView {
|
||||
// MARK: -
|
||||
|
||||
@MainActor
|
||||
func trackDownloads() {
|
||||
func startTracking() {
|
||||
Task { [weak self, backupAttachmentDownloadTracker] in
|
||||
for await downloadUpdate in backupAttachmentDownloadTracker.updates() {
|
||||
guard let self else { return }
|
||||
@ -167,7 +167,7 @@ public class CLVBackupDownloadProgressView {
|
||||
}
|
||||
case .restoring, .wifiNotReachable, .paused, .outOfDiskSpace:
|
||||
if state.deviceSleepBlock == nil {
|
||||
let deviceSleepBlock = DeviceSleepBlockObject(blockReason: "BackupAttachmentDownloadProgressView")
|
||||
let deviceSleepBlock = DeviceSleepBlockObject(blockReason: "CLVBackupDownloadProgressView")
|
||||
state.deviceSleepBlock = deviceSleepBlock
|
||||
deviceSleepManager.addBlock(blockObject: deviceSleepBlock)
|
||||
}
|
||||
@ -1042,68 +1042,6 @@ private class BackupAttachmentDownloadProgressView: UIView {
|
||||
)
|
||||
static var resumeButtonFont: UIFont { .dynamicTypeSubheadlineClamped.bold() }
|
||||
}
|
||||
|
||||
// MARK: ArcView
|
||||
|
||||
private class ArcView: UIView {
|
||||
|
||||
var percentComplete: Float = 0 {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
self.isOpaque = false
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("Unimplemented")
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
let center = CGPoint(x: rect.midX, y: rect.midY)
|
||||
let lineWidth: CGFloat = 3
|
||||
let radius = min(rect.width, rect.height) / 2 - lineWidth / 2
|
||||
|
||||
context.setStrokeColor(UIColor.Signal.secondaryLabel.cgColor)
|
||||
context.setLineWidth(lineWidth)
|
||||
context.setLineCap(.round)
|
||||
|
||||
context.addArc(
|
||||
center: center,
|
||||
radius: radius,
|
||||
startAngle: 0,
|
||||
endAngle: 2 * .pi,
|
||||
clockwise: false,
|
||||
)
|
||||
|
||||
context.strokePath()
|
||||
|
||||
let startAngle: CGFloat = -.pi / 2
|
||||
let endAngle = 2 * .pi * CGFloat(percentComplete)
|
||||
context.setStrokeColor(UIColor.Signal.ultramarine.cgColor)
|
||||
|
||||
context.addArc(
|
||||
center: center,
|
||||
radius: radius,
|
||||
startAngle: startAngle,
|
||||
endAngle: endAngle + startAngle,
|
||||
clockwise: false,
|
||||
)
|
||||
|
||||
context.strokePath()
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@ -0,0 +1,586 @@
|
||||
//
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import PureLayout
|
||||
import SignalServiceKit
|
||||
import UIKit
|
||||
|
||||
class CLVBackupProgressView {
|
||||
|
||||
private struct State {
|
||||
var updateStreamTasks: [Task<Void, Never>] = []
|
||||
|
||||
var isVisible: Bool = false
|
||||
var deviceSleepBlock: DeviceSleepBlockObject?
|
||||
|
||||
var lastUploadTrackerUpdate: BackupAttachmentUploadTracker.UploadUpdate?
|
||||
var lastExportJobUpdate: BackupExportJobRunnerUpdate?
|
||||
}
|
||||
|
||||
private let backupAttachmentUploadTracker: BackupAttachmentUploadTracker
|
||||
private let backupExportJobRunner: BackupExportJobRunner
|
||||
private let db: DB
|
||||
private let deviceSleepManager: DeviceSleepManager
|
||||
|
||||
weak var chatListViewController: ChatListViewController?
|
||||
let backupProgressViewCell: UITableViewCell
|
||||
|
||||
private let backupProgressView: BackupProgressView
|
||||
private let state: AtomicValue<State>
|
||||
|
||||
init() {
|
||||
self.backupAttachmentUploadTracker = AppEnvironment.shared.backupAttachmentUploadTracker
|
||||
self.backupExportJobRunner = DependenciesBridge.shared.backupExportJobRunner
|
||||
self.db = DependenciesBridge.shared.db
|
||||
self.deviceSleepManager = DependenciesBridge.shared.deviceSleepManager.owsFailUnwrap("Missing DeviceSleepManager!")
|
||||
|
||||
self.backupProgressViewCell = UITableViewCell()
|
||||
self.backupProgressViewCell.backgroundColor = .Signal.background
|
||||
self.backupProgressView = BackupProgressView(viewState: nil)
|
||||
self.state = AtomicValue(State(), lock: .init())
|
||||
|
||||
self.backupProgressViewCell.contentView.addSubview(self.backupProgressView)
|
||||
self.backupProgressView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(hMargin: 12, vMargin: 12))
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
var shouldBeVisible: Bool {
|
||||
guard BuildFlags.Backups.chatListProgress else {
|
||||
return false
|
||||
}
|
||||
|
||||
return backupProgressView.viewState != nil
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@MainActor
|
||||
func willAppear() {
|
||||
state.update { _state in
|
||||
_state.isVisible = true
|
||||
manageDeviceSleepBlock(state: &_state)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func didDisapper() {
|
||||
state.update { _state in
|
||||
_state.isVisible = false
|
||||
manageDeviceSleepBlock(state: &_state)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
func startTracking() {
|
||||
state.update { _state in
|
||||
guard _state.updateStreamTasks.isEmpty else { return }
|
||||
_state.updateStreamTasks = _startTracking()
|
||||
}
|
||||
}
|
||||
|
||||
private func _startTracking() -> [Task<Void, Never>] {
|
||||
return [
|
||||
Task { @MainActor [weak self, backupAttachmentUploadTracker] in
|
||||
for await uploadTrackerUpdate in backupAttachmentUploadTracker.updates() {
|
||||
guard let self else { return }
|
||||
|
||||
state.update { _state in
|
||||
_state.lastUploadTrackerUpdate = uploadTrackerUpdate
|
||||
self.setViewStateForState(state: &_state)
|
||||
}
|
||||
}
|
||||
},
|
||||
Task { @MainActor [weak self, backupExportJobRunner] in
|
||||
for await exportJobUpdate in backupExportJobRunner.updates() {
|
||||
guard let self else { return }
|
||||
|
||||
state.update { _state in
|
||||
_state.lastExportJobUpdate = exportJobUpdate
|
||||
self.setViewStateForState(state: &_state)
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private let chatListReloadQueue = SerialTaskQueue()
|
||||
|
||||
@MainActor
|
||||
private func setViewStateForState(state: inout State) {
|
||||
let oldViewState = backupProgressView.viewState
|
||||
let newViewState = viewStateForState(state: state)
|
||||
|
||||
chatListReloadQueue.enqueue { @MainActor [self] in
|
||||
if oldViewState != newViewState {
|
||||
backupProgressView.viewState = newViewState
|
||||
}
|
||||
|
||||
if (oldViewState == nil) != (newViewState == nil) {
|
||||
// We're hiding/showing the view: reload the chat list.
|
||||
chatListViewController?.loadCoordinator.loadIfNecessary()
|
||||
} else if oldViewState?.id != newViewState?.id {
|
||||
// Our height may change when we change view states, so tell the
|
||||
// table view to recompute.
|
||||
chatListViewController?.tableView.recomputeRowHeights()
|
||||
}
|
||||
}
|
||||
|
||||
manageDeviceSleepBlock(state: &state)
|
||||
}
|
||||
|
||||
private func viewStateForState(state: State) -> BackupProgressView.ViewState? {
|
||||
switch state.lastExportJobUpdate {
|
||||
case .progress(let sequentialProgress):
|
||||
switch sequentialProgress.currentStep {
|
||||
case .backupFileExport, .backupFileUpload:
|
||||
let percentExportCompleted = sequentialProgress.progress(for: .backupFileExport)?.percentComplete ?? 0
|
||||
let percentUploadCompleted = sequentialProgress.progress(for: .backupFileUpload)?.percentComplete ?? 0
|
||||
let percentComplete = (0.95 * percentExportCompleted) + (0.05 * percentUploadCompleted)
|
||||
return .backupFilePreparation(percentComplete: percentComplete)
|
||||
case .attachmentUpload, .attachmentProcessing:
|
||||
break
|
||||
}
|
||||
case nil, .completion:
|
||||
break
|
||||
}
|
||||
|
||||
if let lastUploadTrackerUpdate = state.lastUploadTrackerUpdate {
|
||||
switch lastUploadTrackerUpdate.state {
|
||||
case .running:
|
||||
return .attachmentUploadRunning(
|
||||
bytesUploaded: lastUploadTrackerUpdate.bytesUploaded,
|
||||
totalBytesToUpload: lastUploadTrackerUpdate.totalBytesToUpload,
|
||||
)
|
||||
case .pausedLowBattery:
|
||||
return .attachmentUploadPausedLowBattery
|
||||
case .pausedLowPowerMode:
|
||||
return .attachmentUploadPausedLowPowerMode
|
||||
case .pausedNeedsWifi:
|
||||
return .attachmentUploadPausedNoWifi
|
||||
case .pausedNeedsInternet:
|
||||
return .attachmentUploadPausedNoInternet
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func manageDeviceSleepBlock(state: inout State) {
|
||||
var shouldBlockDeviceSleep = switch backupProgressView.viewState {
|
||||
case .backupFilePreparation: true
|
||||
case .attachmentUploadRunning: true
|
||||
case .attachmentUploadPausedNoWifi: false
|
||||
case .attachmentUploadPausedNoInternet: false
|
||||
case .attachmentUploadPausedLowBattery: false
|
||||
case .attachmentUploadPausedLowPowerMode: false
|
||||
case .complete: false
|
||||
case .failed: false
|
||||
case nil: false
|
||||
}
|
||||
|
||||
shouldBlockDeviceSleep = shouldBlockDeviceSleep && state.isVisible
|
||||
|
||||
if
|
||||
shouldBlockDeviceSleep,
|
||||
state.deviceSleepBlock == nil
|
||||
{
|
||||
let deviceSleepBlock = DeviceSleepBlockObject(blockReason: "CLVBackupProgressView")
|
||||
deviceSleepManager.addBlock(blockObject: deviceSleepBlock)
|
||||
state.deviceSleepBlock = deviceSleepBlock
|
||||
} else if
|
||||
!shouldBlockDeviceSleep,
|
||||
let deviceSleepBlock = state.deviceSleepBlock.take()
|
||||
{
|
||||
deviceSleepManager.removeBlock(blockObject: deviceSleepBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private class BackupProgressView: UIView {
|
||||
|
||||
enum ViewState: Equatable, Identifiable {
|
||||
case backupFilePreparation(percentComplete: Float)
|
||||
case attachmentUploadRunning(bytesUploaded: UInt64, totalBytesToUpload: UInt64)
|
||||
case attachmentUploadPausedNoWifi
|
||||
case attachmentUploadPausedNoInternet
|
||||
case attachmentUploadPausedLowBattery
|
||||
case attachmentUploadPausedLowPowerMode
|
||||
case complete
|
||||
case failed
|
||||
|
||||
var id: String {
|
||||
return switch self {
|
||||
case .backupFilePreparation: "backupFilePreparation"
|
||||
case .attachmentUploadRunning: "attachmentUploadRunning"
|
||||
case .attachmentUploadPausedNoWifi: "attachmentUploadPausedNoWifi"
|
||||
case .attachmentUploadPausedNoInternet: "attachmentUploadPausedNoInternet"
|
||||
case .attachmentUploadPausedLowBattery: "attachmentUploadPausedLowBattery"
|
||||
case .attachmentUploadPausedLowPowerMode: "attachmentUploadPausedLowPowerMode"
|
||||
case .complete: "complete"
|
||||
case .failed: "failed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var viewState: ViewState? {
|
||||
didSet {
|
||||
configureSubviewsForCurrentState()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private static func configure(label: UILabel, color: UIColor) {
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.adjustsFontForContentSizeCategory = true
|
||||
label.font = .dynamicTypeSubheadline
|
||||
label.textColor = color
|
||||
label.numberOfLines = 0
|
||||
label.lineBreakMode = .byWordWrapping
|
||||
}
|
||||
|
||||
private let leadingAccessoryImageView = UIImageView()
|
||||
|
||||
private let labelStackView = UIStackView()
|
||||
private let titleLabel = UILabel()
|
||||
private let progressLabel = UILabel()
|
||||
|
||||
/// A container for the various trailingAccessory views we might display. A
|
||||
/// stack view so we can use `isHidden = true` to make subviews take up zero
|
||||
/// space.
|
||||
private let trailingAccessoryContainerView = UIStackView()
|
||||
private let trailingAccessorySpacerView = UIView()
|
||||
private let trailingAccessoryRunningArcView = ArcView()
|
||||
private let trailingAccessoryPausedWifiResumeButton = UIButton()
|
||||
private let trailingAccessoryPausedNoInternetLabel = UILabel()
|
||||
private let trailingAccessoryPausedLowBatteryLabel = UILabel()
|
||||
private let trailingAccessoryPausedLowPowerModeLabel = UILabel()
|
||||
private let trailingAccessoryCompleteDismissButton = UIButton()
|
||||
private let trailingAccessoryFailedDetailsButton = UIButton()
|
||||
|
||||
init(viewState: ViewState?) {
|
||||
self.viewState = viewState
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
backgroundColor = .Signal.quaternaryFill
|
||||
layer.cornerRadius = 24
|
||||
layoutMargins = UIEdgeInsets(hMargin: 16, vMargin: 12)
|
||||
|
||||
addSubview(leadingAccessoryImageView)
|
||||
leadingAccessoryImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
addSubview(labelStackView)
|
||||
labelStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
labelStackView.axis = .vertical
|
||||
labelStackView.spacing = 4
|
||||
|
||||
labelStackView.addArrangedSubview(titleLabel)
|
||||
Self.configure(label: titleLabel, color: .Signal.label)
|
||||
|
||||
labelStackView.addArrangedSubview(progressLabel)
|
||||
Self.configure(label: progressLabel, color: .Signal.secondaryLabel)
|
||||
|
||||
addSubview(trailingAccessoryContainerView)
|
||||
trailingAccessoryContainerView.alignment = .trailing
|
||||
trailingAccessoryContainerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
trailingAccessoryContainerView.addArrangedSubview(trailingAccessorySpacerView)
|
||||
trailingAccessorySpacerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
trailingAccessoryContainerView.addArrangedSubview(trailingAccessoryRunningArcView)
|
||||
trailingAccessoryRunningArcView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
trailingAccessoryContainerView.addArrangedSubview(trailingAccessoryPausedWifiResumeButton)
|
||||
trailingAccessoryPausedWifiResumeButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
trailingAccessoryPausedWifiResumeButton.setTitle(
|
||||
"Resume",
|
||||
for: .normal,
|
||||
)
|
||||
trailingAccessoryPausedWifiResumeButton.setTitleColor(.Signal.label, for: .normal)
|
||||
trailingAccessoryPausedWifiResumeButton.titleLabel?.font = .dynamicTypeSubheadline.semibold()
|
||||
trailingAccessoryPausedWifiResumeButton.titleLabel?.adjustsFontForContentSizeCategory = true
|
||||
trailingAccessoryPausedWifiResumeButton.addAction(
|
||||
UIAction { [weak self] _ in
|
||||
self?.didTapPausedWifiResumeButton()
|
||||
},
|
||||
for: .touchUpInside,
|
||||
)
|
||||
|
||||
trailingAccessoryContainerView.addArrangedSubview(trailingAccessoryCompleteDismissButton)
|
||||
trailingAccessoryCompleteDismissButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
trailingAccessoryCompleteDismissButton.setImage(.x, animated: false)
|
||||
trailingAccessoryCompleteDismissButton.tintColor = .Signal.secondaryLabel
|
||||
trailingAccessoryCompleteDismissButton.addAction(
|
||||
UIAction { [weak self] _ in
|
||||
self?.didTapDismissButton()
|
||||
},
|
||||
for: .touchUpInside,
|
||||
)
|
||||
|
||||
trailingAccessoryContainerView.addArrangedSubview(trailingAccessoryPausedNoInternetLabel)
|
||||
Self.configure(label: trailingAccessoryPausedNoInternetLabel, color: .Signal.secondaryLabel)
|
||||
trailingAccessoryPausedNoInternetLabel.text = "No Internet…"
|
||||
|
||||
trailingAccessoryContainerView.addArrangedSubview(trailingAccessoryPausedLowBatteryLabel)
|
||||
Self.configure(label: trailingAccessoryPausedLowBatteryLabel, color: .Signal.secondaryLabel)
|
||||
trailingAccessoryPausedLowBatteryLabel.text = "Low Battery…"
|
||||
|
||||
trailingAccessoryContainerView.addArrangedSubview(trailingAccessoryPausedLowPowerModeLabel)
|
||||
Self.configure(label: trailingAccessoryPausedLowPowerModeLabel, color: .Signal.secondaryLabel)
|
||||
trailingAccessoryPausedLowPowerModeLabel.text = "Low Power Mode…"
|
||||
|
||||
trailingAccessoryContainerView.addArrangedSubview(trailingAccessoryFailedDetailsButton)
|
||||
trailingAccessoryFailedDetailsButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
trailingAccessoryFailedDetailsButton.setTitle(
|
||||
"See Details",
|
||||
for: .normal,
|
||||
)
|
||||
trailingAccessoryFailedDetailsButton.setTitleColor(.Signal.label, for: .normal)
|
||||
trailingAccessoryFailedDetailsButton.titleLabel?.font = .dynamicTypeSubheadline.semibold()
|
||||
trailingAccessoryFailedDetailsButton.titleLabel?.adjustsFontForContentSizeCategory = true
|
||||
trailingAccessoryFailedDetailsButton.addAction(
|
||||
UIAction { [weak self] _ in
|
||||
self?.didTapFailedDetailsButton()
|
||||
},
|
||||
for: .touchUpInside,
|
||||
)
|
||||
|
||||
initializeConstraints()
|
||||
configureSubviewsForCurrentState()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
owsFail("Not implemented!")
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private func configureSubviewsForCurrentState() {
|
||||
// Leading accessory
|
||||
switch viewState {
|
||||
case nil,
|
||||
.backupFilePreparation,
|
||||
.attachmentUploadRunning,
|
||||
.attachmentUploadPausedNoWifi,
|
||||
.attachmentUploadPausedNoInternet,
|
||||
.attachmentUploadPausedLowBattery,
|
||||
.attachmentUploadPausedLowPowerMode:
|
||||
leadingAccessoryImageView.image = .backup
|
||||
leadingAccessoryImageView.tintColor = .Signal.label
|
||||
case .complete:
|
||||
leadingAccessoryImageView.image = .checkCircle
|
||||
leadingAccessoryImageView.tintColor = .Signal.ultramarine
|
||||
case .failed:
|
||||
leadingAccessoryImageView.image = .errorCircle
|
||||
leadingAccessoryImageView.tintColor = .Signal.orange
|
||||
}
|
||||
|
||||
// Labels
|
||||
let titleLabelText: String
|
||||
var progressLabelText: String?
|
||||
switch viewState {
|
||||
case .backupFilePreparation(let percentComplete):
|
||||
titleLabelText = "Preparing backup"
|
||||
progressLabelText = percentComplete.formatted(.owsPercent())
|
||||
case .attachmentUploadRunning(let bytesUploaded, let totalBytesToUpload):
|
||||
titleLabelText = "Uploading backup"
|
||||
progressLabelText = String(
|
||||
format: "%1$@ of %2$@",
|
||||
bytesUploaded.formatted(.owsByteCount()),
|
||||
totalBytesToUpload.formatted(.owsByteCount()),
|
||||
)
|
||||
case .attachmentUploadPausedNoWifi:
|
||||
titleLabelText = "Waiting for Wi-Fi"
|
||||
case .attachmentUploadPausedNoInternet:
|
||||
titleLabelText = "Backup paused"
|
||||
case .attachmentUploadPausedLowBattery:
|
||||
titleLabelText = "Backup paused"
|
||||
case .attachmentUploadPausedLowPowerMode:
|
||||
titleLabelText = "Backup paused"
|
||||
case .complete:
|
||||
titleLabelText = "Backup complete"
|
||||
case .failed:
|
||||
titleLabelText = "Backup failed"
|
||||
case nil:
|
||||
titleLabelText = ""
|
||||
}
|
||||
titleLabel.text = titleLabelText
|
||||
if let progressLabelText {
|
||||
progressLabel.text = progressLabelText
|
||||
progressLabel.isHidden = false
|
||||
} else {
|
||||
progressLabel.isHidden = true
|
||||
}
|
||||
|
||||
// Trailing accessory
|
||||
let trailingAccessoryView: UIView?
|
||||
switch viewState {
|
||||
case .backupFilePreparation(let percentComplete):
|
||||
trailingAccessoryRunningArcView.percentComplete = percentComplete
|
||||
trailingAccessoryView = trailingAccessoryRunningArcView
|
||||
case .attachmentUploadRunning(let bytesUploaded, let totalBytesToUpload):
|
||||
trailingAccessoryRunningArcView.percentComplete = Float(bytesUploaded) / Float(totalBytesToUpload)
|
||||
trailingAccessoryView = trailingAccessoryRunningArcView
|
||||
case .attachmentUploadPausedNoWifi:
|
||||
trailingAccessoryView = trailingAccessoryPausedWifiResumeButton
|
||||
case .attachmentUploadPausedNoInternet:
|
||||
trailingAccessoryView = trailingAccessoryPausedNoInternetLabel
|
||||
case .attachmentUploadPausedLowBattery:
|
||||
trailingAccessoryView = trailingAccessoryPausedLowBatteryLabel
|
||||
case .attachmentUploadPausedLowPowerMode:
|
||||
trailingAccessoryView = trailingAccessoryPausedLowPowerModeLabel
|
||||
case .complete:
|
||||
trailingAccessoryView = trailingAccessoryCompleteDismissButton
|
||||
case .failed:
|
||||
trailingAccessoryView = trailingAccessoryFailedDetailsButton
|
||||
case nil:
|
||||
trailingAccessoryView = nil
|
||||
}
|
||||
|
||||
// Hide all but at most one trailingAccessory view.
|
||||
for view in trailingAccessoryContainerView.arrangedSubviews {
|
||||
if view == trailingAccessoryView || view == trailingAccessorySpacerView {
|
||||
view.isHidden = false
|
||||
} else {
|
||||
view.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private func initializeConstraints() {
|
||||
NSLayoutConstraint.activate([
|
||||
leadingAccessoryImageView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
|
||||
leadingAccessoryImageView.centerYAnchor.constraint(equalTo: labelStackView.centerYAnchor),
|
||||
leadingAccessoryImageView.heightAnchor.constraint(equalToConstant: 24),
|
||||
leadingAccessoryImageView.widthAnchor.constraint(equalToConstant: 24),
|
||||
|
||||
labelStackView.leadingAnchor.constraint(equalTo: leadingAccessoryImageView.trailingAnchor, constant: 12),
|
||||
labelStackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
|
||||
labelStackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor),
|
||||
labelStackView.trailingAnchor.constraint(equalTo: trailingAccessoryContainerView.leadingAnchor, constant: -12),
|
||||
|
||||
trailingAccessoryContainerView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
|
||||
trailingAccessoryContainerView.centerYAnchor.constraint(equalTo: labelStackView.centerYAnchor),
|
||||
|
||||
trailingAccessoryRunningArcView.heightAnchor.constraint(equalToConstant: 24),
|
||||
trailingAccessoryRunningArcView.widthAnchor.constraint(equalToConstant: 24),
|
||||
|
||||
trailingAccessoryCompleteDismissButton.heightAnchor.constraint(equalToConstant: 24),
|
||||
trailingAccessoryCompleteDismissButton.widthAnchor.constraint(equalToConstant: 24),
|
||||
])
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private func didTapDismissButton() {
|
||||
// TODO: Implement
|
||||
print(#function)
|
||||
}
|
||||
|
||||
private func didTapPausedWifiResumeButton() {
|
||||
// TODO: Implement
|
||||
print(#function)
|
||||
}
|
||||
|
||||
private func didTapFailedDetailsButton() {
|
||||
// TODO: Implement
|
||||
print(#function)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension ChatListViewController {
|
||||
func handleBackupProgressViewTapped() {
|
||||
// TODO: Implement
|
||||
print(#function)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
#if DEBUG
|
||||
|
||||
private class BackupProgressViewPreviewViewController: UIViewController {
|
||||
private let state: BackupProgressView.ViewState?
|
||||
|
||||
init(state: BackupProgressView.ViewState?) {
|
||||
self.state = state
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError() }
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let progressView = BackupProgressView(viewState: state)
|
||||
view.addSubview(progressView)
|
||||
progressView.autoPinEdgesToSuperviewSafeArea(
|
||||
with: UIEdgeInsets(margin: 16),
|
||||
excludingEdge: .bottom,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 17, *)
|
||||
#Preview("Preparing") {
|
||||
return BackupProgressViewPreviewViewController(state: .backupFilePreparation(percentComplete: 0.33))
|
||||
}
|
||||
|
||||
@available(iOS 17, *)
|
||||
#Preview("Running") {
|
||||
return BackupProgressViewPreviewViewController(state: .attachmentUploadRunning(
|
||||
bytesUploaded: 1_000_000_000,
|
||||
totalBytesToUpload: 2_400_000_000,
|
||||
))
|
||||
}
|
||||
|
||||
@available(iOS 17, *)
|
||||
#Preview("Paused: WiFi") {
|
||||
return BackupProgressViewPreviewViewController(state: .attachmentUploadPausedNoWifi)
|
||||
}
|
||||
|
||||
@available(iOS 17, *)
|
||||
#Preview("Paused: Internet") {
|
||||
return BackupProgressViewPreviewViewController(state: .attachmentUploadPausedNoInternet)
|
||||
}
|
||||
|
||||
@available(iOS 17, *)
|
||||
#Preview("Paused: Battery") {
|
||||
return BackupProgressViewPreviewViewController(state: .attachmentUploadPausedLowBattery)
|
||||
}
|
||||
|
||||
@available(iOS 17, *)
|
||||
#Preview("Paused: Low Power Mode") {
|
||||
return BackupProgressViewPreviewViewController(state: .attachmentUploadPausedLowPowerMode)
|
||||
}
|
||||
|
||||
@available(iOS 17, *)
|
||||
#Preview("Complete") {
|
||||
return BackupProgressViewPreviewViewController(state: .complete)
|
||||
}
|
||||
|
||||
@available(iOS 17, *)
|
||||
#Preview("Failed") {
|
||||
return BackupProgressViewPreviewViewController(state: .failed)
|
||||
}
|
||||
|
||||
@available(iOS 17, *)
|
||||
#Preview("Nil") {
|
||||
return BackupProgressViewPreviewViewController(state: nil)
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -286,6 +286,7 @@ public class CLVLoadCoordinator {
|
||||
lastSelectedThreadId: String?,
|
||||
hasVisibleReminders: Bool,
|
||||
shouldBackupDownloadProgressViewBeVisible: Bool,
|
||||
shouldBackupProgressViewBeVisible: Bool,
|
||||
lastViewInfo: CLVViewInfo,
|
||||
transaction: DBReadTransaction,
|
||||
) -> CLVLoadInfo {
|
||||
@ -298,6 +299,7 @@ public class CLVLoadCoordinator {
|
||||
lastSelectedThreadId: lastSelectedThreadId,
|
||||
hasVisibleReminders: hasVisibleReminders,
|
||||
shouldBackupDownloadProgressViewBeVisible: shouldBackupDownloadProgressViewBeVisible,
|
||||
shouldBackupProgressViewBeVisible: shouldBackupProgressViewBeVisible,
|
||||
transaction: transaction,
|
||||
)
|
||||
|
||||
@ -388,9 +390,6 @@ public class CLVLoadCoordinator {
|
||||
|
||||
// Copy the "current" load info, reset "next" load info.
|
||||
|
||||
let hasVisibleReminders = viewController.viewState.reminderViews.hasVisibleReminders
|
||||
let shouldBackupDownloadProgressViewBeVisible = viewController.viewState.backupDownloadProgressView.shouldBeVisible
|
||||
|
||||
let loadResult: CLVLoadResult = SSKEnvironment.shared.databaseStorageRef.read { transaction in
|
||||
// Decide what kind of load we prefer.
|
||||
let loadInfo = loadInfoBuilder.build(
|
||||
@ -399,8 +398,9 @@ public class CLVLoadCoordinator {
|
||||
inboxFilter: viewController.viewState.inboxFilter,
|
||||
isMultiselectActive: viewController.viewState.multiSelectState.isActive,
|
||||
lastSelectedThreadId: viewController.viewState.lastSelectedThreadId,
|
||||
hasVisibleReminders: hasVisibleReminders,
|
||||
shouldBackupDownloadProgressViewBeVisible: shouldBackupDownloadProgressViewBeVisible,
|
||||
hasVisibleReminders: viewController.viewState.reminderViews.hasVisibleReminders,
|
||||
shouldBackupDownloadProgressViewBeVisible: viewController.viewState.backupDownloadProgressView.shouldBeVisible,
|
||||
shouldBackupProgressViewBeVisible: viewController.viewState.backupProgressView.shouldBeVisible,
|
||||
lastViewInfo: viewController.renderState.viewInfo,
|
||||
transaction: transaction,
|
||||
)
|
||||
|
||||
@ -246,10 +246,6 @@ extension ChatListViewController {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateDownloadProgressView() {
|
||||
viewState.backupDownloadProgressView.trackDownloads()
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
public func updateBackupFailureAlertsWithSneakyTransaction() {
|
||||
|
||||
@ -24,6 +24,7 @@ public class ChatListViewController: OWSViewController, HomeTabViewController {
|
||||
loadCoordinator.viewController = self
|
||||
viewState.reminderViews.chatListViewController = self
|
||||
viewState.backupDownloadProgressView.chatListViewController = self
|
||||
viewState.backupProgressView.chatListViewController = self
|
||||
viewState.settingsButtonCreator.delegate = self
|
||||
viewState.proxyButtonCreator.delegate = self
|
||||
viewState.configure()
|
||||
@ -89,6 +90,10 @@ public class ChatListViewController: OWSViewController, HomeTabViewController {
|
||||
viewState.searchController.searchResultsUpdater = self
|
||||
searchResultsController.delegate = self
|
||||
|
||||
// Backups
|
||||
viewState.backupDownloadProgressView.startTracking()
|
||||
viewState.backupProgressView.startTracking()
|
||||
|
||||
updateBarButtonItems()
|
||||
updateArchiveReminderView()
|
||||
updateRegistrationReminderView()
|
||||
@ -96,7 +101,6 @@ public class ChatListViewController: OWSViewController, HomeTabViewController {
|
||||
updateExpirationReminderView()
|
||||
updatePaymentReminderView()
|
||||
updateUsernameReminderView()
|
||||
updateDownloadProgressView()
|
||||
updateTableViewPaddingIfNeeded()
|
||||
observeNotifications()
|
||||
}
|
||||
@ -167,6 +171,7 @@ public class ChatListViewController: OWSViewController, HomeTabViewController {
|
||||
|
||||
viewState.searchResultsController.viewWillAppear(animated)
|
||||
viewState.backupDownloadProgressView.willAppear()
|
||||
viewState.backupProgressView.willAppear()
|
||||
|
||||
updateUnreadPaymentNotificationsCountWithSneakyTransaction()
|
||||
|
||||
@ -282,6 +287,7 @@ public class ChatListViewController: OWSViewController, HomeTabViewController {
|
||||
|
||||
searchResultsController.viewDidDisappear(animated)
|
||||
viewState.backupDownloadProgressView.didDisappear()
|
||||
viewState.backupProgressView.didDisapper()
|
||||
}
|
||||
|
||||
override public func viewIsAppearing(_ animated: Bool) {
|
||||
|
||||
@ -60,8 +60,7 @@ class NameCollisionResolutionViewController: OWSTableViewController2 {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
coordinator.animate { _ in
|
||||
// Force tableview to recalculate self-sized cell height
|
||||
self.tableView.beginUpdates()
|
||||
self.tableView.endUpdates()
|
||||
self.tableView.recomputeRowHeights()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
67
Signal/src/views/ArcView.swift
Normal file
67
Signal/src/views/ArcView.swift
Normal file
@ -0,0 +1,67 @@
|
||||
//
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/// A torus, whose ring is filled up to `percentComplete` with blue. Useful as a
|
||||
/// square-aspect-ratio progress indicator.
|
||||
class ArcView: UIView {
|
||||
var percentComplete: Float = 0 {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
self.isOpaque = false
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("Unimplemented")
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
let center = CGPoint(x: rect.midX, y: rect.midY)
|
||||
let lineWidth: CGFloat = 3
|
||||
let radius = min(rect.width, rect.height) / 2 - lineWidth / 2
|
||||
|
||||
context.setStrokeColor(UIColor.Signal.tertiaryLabel.cgColor)
|
||||
context.setLineWidth(lineWidth)
|
||||
context.setLineCap(.round)
|
||||
|
||||
context.addArc(
|
||||
center: center,
|
||||
radius: radius,
|
||||
startAngle: 0,
|
||||
endAngle: 2 * .pi,
|
||||
clockwise: false,
|
||||
)
|
||||
|
||||
context.strokePath()
|
||||
|
||||
let startAngle: CGFloat = -.pi / 2
|
||||
let endAngle = 2 * .pi * CGFloat(percentComplete)
|
||||
context.setStrokeColor(UIColor.Signal.ultramarine.cgColor)
|
||||
|
||||
context.addArc(
|
||||
center: center,
|
||||
radius: radius,
|
||||
startAngle: startAngle,
|
||||
endAngle: endAngle + startAngle,
|
||||
clockwise: false,
|
||||
)
|
||||
|
||||
context.strokePath()
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
@ -51,6 +51,8 @@ public enum BuildFlags {
|
||||
|
||||
public static let useLowerDefaultListMediaRefreshInterval = build <= .beta
|
||||
public static let performListMediaIntegrityChecks = build <= .beta
|
||||
|
||||
public static let chatListProgress = build <= .dev
|
||||
}
|
||||
|
||||
public static let callQualitySurvey = true
|
||||
|
||||
@ -7,10 +7,25 @@ extension Optional {
|
||||
|
||||
public func mapAsync<T>(_ fn: (Wrapped) async throws -> T) async rethrows -> T? {
|
||||
switch self {
|
||||
case .none:
|
||||
case nil:
|
||||
return nil
|
||||
case .some(let v):
|
||||
return try await fn(v)
|
||||
}
|
||||
}
|
||||
|
||||
public func owsFailUnwrap(
|
||||
_ message: String,
|
||||
logger: PrefixedLogger = .empty(),
|
||||
file: String = #file,
|
||||
function: String = #function,
|
||||
line: Int = #line,
|
||||
) -> Wrapped {
|
||||
switch self {
|
||||
case nil:
|
||||
owsFail(message, logger: logger, file: file, function: function, line: line)
|
||||
case .some(let value):
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
SignalUI/UIKitExtensions/UITableView+RowHeights.swift
Normal file
16
SignalUI/UIKitExtensions/UITableView+RowHeights.swift
Normal file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
extension UITableView {
|
||||
/// Force the table view to recompute the height of its rows.
|
||||
///
|
||||
/// Useful for tables with rows that use AutoLayout and need to tell their
|
||||
/// owning table that their height may have changed, for example due to the
|
||||
/// contents of the cell having changed.
|
||||
public func recomputeRowHeights() {
|
||||
beginUpdates()
|
||||
endUpdates()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user