diff --git a/enzevalos_iphone.xcodeproj/project.pbxproj b/enzevalos_iphone.xcodeproj/project.pbxproj index bdfaad849cc607f19adb8c8fc06bdcdcf9d439fd..cd445386cf1d42eab8f3da50006d492881230081 100644 --- a/enzevalos_iphone.xcodeproj/project.pbxproj +++ b/enzevalos_iphone.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 3EB4FAA420120096001D0625 /* DialogOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EB4FAA320120096001D0625 /* DialogOption.swift */; }; 3EC35F2420037651008BDF95 /* InvitationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EC35F2320037651008BDF95 /* InvitationHelper.swift */; }; 3EC35F302003838E008BDF95 /* InvitationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EC35F2F2003838E008BDF95 /* InvitationTests.swift */; }; + 3F071ECF2611B03A00E121F0 /* RefreshModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F071ECE2611B03A00E121F0 /* RefreshModel.swift */; }; + 3F071ED32611BF6900E121F0 /* UpdateMailsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F071ED22611BF6900E121F0 /* UpdateMailsModel.swift */; }; 3FB75DC525FFA75C00919925 /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB75DC425FFA75B00919925 /* ComposeView.swift */; }; 3FB75DC925FFA77800919925 /* RecipientRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB75DC825FFA77800919925 /* RecipientRowView.swift */; }; 3FB75DCD25FFD37400919925 /* RecipientListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB75DCC25FFD37400919925 /* RecipientListView.swift */; }; @@ -70,7 +72,7 @@ 476406822416AA6F00C7D426 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 4764060F2416AA6F00C7D426 /* LICENSE */; }; 476406952416B54D00C7D426 /* KeyRecordRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 476406892416B54D00C7D426 /* KeyRecordRow.swift */; }; 476406972416B54D00C7D426 /* InboxCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4764068B2416B54D00C7D426 /* InboxCoordinator.swift */; }; - 476406982416B54D00C7D426 /* CiricleImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4764068D2416B54D00C7D426 /* CiricleImage.swift */; }; + 476406982416B54D00C7D426 /* CircleImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4764068D2416B54D00C7D426 /* CircleImage.swift */; }; 476406992416B54D00C7D426 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4764068E2416B54D00C7D426 /* SearchView.swift */; }; 4764069A2416B54D00C7D426 /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4764068F2416B54D00C7D426 /* MailView.swift */; }; 4764069C2416B54D00C7D426 /* VCSwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 476406922416B54D00C7D426 /* VCSwiftUIView.swift */; }; @@ -302,6 +304,8 @@ 3EB4FAA320120096001D0625 /* DialogOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DialogOption.swift; sourceTree = "<group>"; }; 3EC35F2320037651008BDF95 /* InvitationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InvitationHelper.swift; path = Invitation/InvitationHelper.swift; sourceTree = "<group>"; }; 3EC35F2F2003838E008BDF95 /* InvitationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitationTests.swift; sourceTree = "<group>"; }; + 3F071ECE2611B03A00E121F0 /* RefreshModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshModel.swift; sourceTree = "<group>"; }; + 3F071ED22611BF6900E121F0 /* UpdateMailsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateMailsModel.swift; sourceTree = "<group>"; }; 3FB75DC425FFA75B00919925 /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = "<group>"; }; 3FB75DC825FFA77800919925 /* RecipientRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientRowView.swift; sourceTree = "<group>"; }; 3FB75DCC25FFD37400919925 /* RecipientListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipientListView.swift; sourceTree = "<group>"; }; @@ -456,7 +460,7 @@ 4764067D2416AA6F00C7D426 /* asn1err.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = asn1err.h; sourceTree = "<group>"; }; 476406892416B54D00C7D426 /* KeyRecordRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyRecordRow.swift; sourceTree = "<group>"; }; 4764068B2416B54D00C7D426 /* InboxCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InboxCoordinator.swift; sourceTree = "<group>"; }; - 4764068D2416B54D00C7D426 /* CiricleImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CiricleImage.swift; sourceTree = "<group>"; }; + 4764068D2416B54D00C7D426 /* CircleImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleImage.swift; sourceTree = "<group>"; }; 4764068E2416B54D00C7D426 /* SearchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; }; 4764068F2416B54D00C7D426 /* MailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = "<group>"; }; 476406922416B54D00C7D426 /* VCSwiftUIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VCSwiftUIView.swift; sourceTree = "<group>"; }; @@ -530,7 +534,7 @@ 47C112C92531E9B000621A07 /* AttachmentRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentRecord.swift; sourceTree = "<group>"; }; 47C22280218AFD6300BD2C2B /* AutocryptTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocryptTest.swift; sourceTree = "<group>"; }; 47C22282218B02C700BD2C2B /* autocryptSimpleExample1.eml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = autocryptSimpleExample1.eml; sourceTree = "<group>"; }; - 47C3490125489F52008D290C /* MailRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MailRowView.swift; path = enzevalos_iphone/SwiftUI/SupportingViews/MailRowView.swift; sourceTree = SOURCE_ROOT; }; + 47C3490125489F52008D290C /* MailRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MailRowView.swift; path = enzevalos_iphone/SwiftUI/Inbox/MailRowView.swift; sourceTree = SOURCE_ROOT; }; 47C8224324379EAE005BCE73 /* AttachmentsViewMain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentsViewMain.swift; sourceTree = "<group>"; }; 47C8224924379EAE005BCE73 /* DialogView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DialogView.swift; sourceTree = "<group>"; }; 47C8224A24379EAE005BCE73 /* FloatingActionButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FloatingActionButton.swift; sourceTree = "<group>"; }; @@ -967,6 +971,8 @@ 4750BDE02539C5FC00F6D5AB /* InboxView.swift */, 47FA8EAB254D77DE006883D0 /* MailListView.swift */, 47C3490125489F52008D290C /* MailRowView.swift */, + 3F071ECE2611B03A00E121F0 /* RefreshModel.swift */, + 3F071ED22611BF6900E121F0 /* UpdateMailsModel.swift */, ); path = Inbox; sourceTree = "<group>"; @@ -979,7 +985,7 @@ 47C822612438A81C005BCE73 /* MapView.swift */, 47EABF2A2423C20C00774A93 /* LoadingBlocker.swift */, 47EABF282423C1FB00774A93 /* KeyboardChecker.swift */, - 4764068D2416B54D00C7D426 /* CiricleImage.swift */, + 4764068D2416B54D00C7D426 /* CircleImage.swift */, 4764068E2416B54D00C7D426 /* SearchView.swift */, 4764068F2416B54D00C7D426 /* MailView.swift */, 476406902416B54D00C7D426 /* SwiftUI to UIKit */, @@ -1888,6 +1894,7 @@ 4775D7AC243F18BC0052F2CC /* SecurityBriefingView.swift in Sources */, 971D404A2428C87E002FCD31 /* BadgeCaseView.swift in Sources */, 47C8225B24379EAE005BCE73 /* MessageViewMain.swift in Sources */, + 3F071ED32611BF6900E121F0 /* UpdateMailsModel.swift in Sources */, 47FAE30E2524AA97005A1BCB /* DataModel.xcdatamodeld in Sources */, 47A5D6E22294BF3B0084F81D /* TempKey.swift in Sources */, 47C8226B2438A86B005BCE73 /* SenderViewMain.swift in Sources */, @@ -1956,13 +1963,14 @@ 47C8225324379EAE005BCE73 /* AttachmentsViewMain.swift in Sources */, 678942612430C3D600C746D1 /* Typosquatting.swift in Sources */, 47C112CA2531E9B000621A07 /* AttachmentRecord.swift in Sources */, - 476406982416B54D00C7D426 /* CiricleImage.swift in Sources */, + 476406982416B54D00C7D426 /* CircleImage.swift in Sources */, A111F6AD1FA77B170060AFDE /* LoggerDetail.swift in Sources */, 47358D92244A5AEA000116D7 /* SelectableTextView.swift in Sources */, 0EF148052422543E00B3C198 /* Certificate.swift in Sources */, 47BCAF5A259DE6770008FE4B /* SecretKeyListView.swift in Sources */, A15D215B223BE5F4003E0CE0 /* TempAttachment.swift in Sources */, 4706D65F225B7B6B00B3F1D3 /* ItunesHandler.swift in Sources */, + 3F071ECF2611B03A00E121F0 /* RefreshModel.swift in Sources */, 47EABF0A241A9C8700774A93 /* AuthenticationViewModel.swift in Sources */, 47FA8EAC254D77DE006883D0 /* MailListView.swift in Sources */, 47C8225924379EAE005BCE73 /* AttPreview.swift in Sources */, diff --git a/enzevalos_iphone/SwiftUI/FolderView/FolderRowView.swift b/enzevalos_iphone/SwiftUI/FolderView/FolderRowView.swift index 557b31f70a9e0dccdd1b693ddc780d36f133cbc4..4fec0752b98267205d105396610fb8ed9d024053 100644 --- a/enzevalos_iphone/SwiftUI/FolderView/FolderRowView.swift +++ b/enzevalos_iphone/SwiftUI/FolderView/FolderRowView.swift @@ -9,8 +9,7 @@ import SwiftUI struct FolderRowView: View { - - //TODO: increase padding for each delimiter? -> Subfolder level? + // TODO: increase padding for each delimiter? -> Subfolder level? public var folder: DisplayFolder var body: some View { HStack { diff --git a/enzevalos_iphone/SwiftUI/Inbox/InboxView.swift b/enzevalos_iphone/SwiftUI/Inbox/InboxView.swift index 421e01e0462e52339a34d84d2e89202bd64e3f0c..2ba3a58f07988c656da24225f43dc626aa06dd6d 100644 --- a/enzevalos_iphone/SwiftUI/Inbox/InboxView.swift +++ b/enzevalos_iphone/SwiftUI/Inbox/InboxView.swift @@ -18,15 +18,24 @@ struct InboxView: View { @State private var updating = false @State private var composeMail = false @State private var goToFolders = false + @ObservedObject var mailUpdater: UpdateMailsModel + + init(folderPath: String, name: String) { + self.folderPath = folderPath + self.name = name + self.mailUpdater = UpdateMailsModel(folderPath: folderPath) + } var body: some View { mailListView - .onAppear(perform: updateMails) + .onAppear(perform: mailUpdater.updateMails) .sheet(isPresented: $composeMail) { ComposeView() } .navigationBarItems(trailing: keyManagementButton) .toolbar { ToolbarItem(placement: .status) { - lastUpdate + Button(action: mailUpdater.updateMails) { + Text(mailUpdater.lastUpdate).font(.callout) + } } ToolbarItemGroup(placement: .bottomBar) { @@ -37,7 +46,7 @@ struct InboxView: View { } private var mailListView: some View { - MailListView(folderPath: folderPath, folderName: name) + MailListView(folderPath: folderPath, folderName: name, mailUpdater: mailUpdater) .environment(\.managedObjectContext, PersistentDataProvider .dataProvider @@ -61,42 +70,42 @@ struct InboxView: View { } } - // TODO: Refactor lastUpdate to model - private var lastUpdate: some View { - var text = NSLocalizedString("Updating", comment: "updating...") - - if !updating { - let last = Date() - let dateFormatter = DateFormatter() - dateFormatter.locale = Locale.current - dateFormatter.timeStyle = .medium - let dateString = dateFormatter.string(from: last) - text = NSLocalizedString("LastUpdate", comment: "") + " " + dateString - } - - return Button(action: updateMails) { - Text(text).font(.callout) - } - } - - - // TODO: Refactor updateMails to model - private func updateMails() { - guard !updating else { - return - } - - LetterboxModel - .instance - .mailHandler - .updateFolder(folderpath: folderPath) { error in - if error == nil { - self.updating = false - } - // TODO: Add error message - } - updating = true - } + // // TODO: Refactor lastUpdate to model + // private var lastUpdate: some View { + // var text = NSLocalizedString("Updating", comment: "updating...") + // + // if !updating { + // let last = Date() + // let dateFormatter = DateFormatter() + // dateFormatter.locale = Locale.current + // dateFormatter.timeStyle = .medium + // let dateString = dateFormatter.string(from: last) + // text = NSLocalizedString("LastUpdate", comment: "") + " " + dateString + // } + // + // return Button(action: updateMails) { + // Text(text).font(.callout) + // } + // } + // + // + // // TODO: Refactor updateMails to model + // private func updateMails() { + // guard !updating else { + // return + // } + // + // LetterboxModel + // .instance + // .mailHandler + // .updateFolder(folderpath: folderPath) { error in + // if error == nil { + // self.updating = false + // } + // // TODO: Add error message + // } + // updating = true + // } private var folderButton: some View { diff --git a/enzevalos_iphone/SwiftUI/Inbox/MailListView.swift b/enzevalos_iphone/SwiftUI/Inbox/MailListView.swift index 6ef156f83112fa561cc55f7faf5920f778092a56..8a4291a301e56b83b8b7645607f90ae4441b651a 100644 --- a/enzevalos_iphone/SwiftUI/Inbox/MailListView.swift +++ b/enzevalos_iphone/SwiftUI/Inbox/MailListView.swift @@ -14,19 +14,22 @@ import CoreData struct MailListView: View { @Environment(\.managedObjectContext) var managedObjectContext + @ObservedObject var model = RefreshModel() var fetchRequest: FetchRequest<MailRecord> var mails: FetchedResults<MailRecord>{fetchRequest.wrappedValue} var folderPath: String var folderName: String + var mailUpdater: UpdateMailsModel @State private var showUser = false @State private var searchText = "" @State private var searchType = SearchType.All - init(folderPath: String, folderName: String) { + init(folderPath: String, folderName: String, mailUpdater: UpdateMailsModel) { fetchRequest = MailRecord.mailsInFolderFetchRequest(folderpath: folderPath) self.folderPath = folderPath self.folderName = folderName + self.mailUpdater = mailUpdater } var body: some View { @@ -34,7 +37,15 @@ struct MailListView: View { SearchView(searchText: $searchText, searchType: $searchType) .padding() - mailList + ScrollView { + measurements.frame(width: 0, height: 0) + + VStack { + pullToRefreshIndicator + mailList + } + .offset(y: model.released ? 50 : -10) + } } .navigationBarTitle(folderName, displayMode: .inline) } @@ -53,6 +64,54 @@ struct MailListView: View { .listStyle(PlainListStyle()) } + @ViewBuilder + private var pullToRefreshIndicator: some View { + if model.started && model.released { + ProgressView() + .offset(y: -32) + } else { + Image(systemName: "arrow.down.circle") + .foregroundColor(.secondary) + .font(.system(size: 28, weight: .bold)) + .rotationEffect(Angle(degrees: model.started ? 180 : 0)) + .offset(y: -32) + .animation(.easeIn) + } + } + + private var measurements: GeometryReader<AnyView> { + GeometryReader { geo -> AnyView in + DispatchQueue.main.async { + if model.startOffset == 0 { + model.startOffset = geo.frame(in: .global).minY + } + + model.offset = geo.frame(in: .global).minY + + if model.offset - model.startOffset > 80 && !model.started { + model.started = true + } + + if model.offset == model.startOffset && model.started { + + if !model.released { + withAnimation(.linear) { + model.released = true + model.refresh(perform: mailUpdater.updateMails) + } + } else { + if model.disabled { + model.disabled = false + model.refresh(perform: mailUpdater.updateMails) + } + } + } + } + + return AnyView(EmptyView()) + } + } + func filterKeyRecord(keyRecord: MailRecord) -> Bool { if self.searchText.isEmpty || self.searchText == NSLocalizedString("Searchbar.Title", comment: "Search") { @@ -79,10 +138,3 @@ struct MailListView: View { return false } } - -struct MailListView_Previews: PreviewProvider { - static var previews: some View { - MailListView(folderPath: "INBOX", folderName: "INBOX") - .environment(\.managedObjectContext, PersistentDataProvider.proxyPersistentDataProvider.persistentContainer.viewContext) - } -} diff --git a/enzevalos_iphone/SwiftUI/Inbox/RefreshModel.swift b/enzevalos_iphone/SwiftUI/Inbox/RefreshModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..e58f5fdf07741fd26ae3f1dc7b44b44da2a0ed6f --- /dev/null +++ b/enzevalos_iphone/SwiftUI/Inbox/RefreshModel.swift @@ -0,0 +1,29 @@ +// +// RefreshModel.swift +// enzevalos_iphone +// +// Created by Chris Offner on 29.03.21. +// Copyright © 2021 fu-berlin. All rights reserved. +// + +import Foundation + +class RefreshModel: ObservableObject { + var startOffset: CGFloat = 0 + var offset: CGFloat = 0 + var started = false + var released = false + var disabled = false + + func refresh(perform action: @escaping () -> Void) { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + if self.offset == self.startOffset { + action() + self.released = false + self.started = false + } else { + self.disabled = true + } + } + } +} diff --git a/enzevalos_iphone/SwiftUI/Inbox/UpdateMailsModel.swift b/enzevalos_iphone/SwiftUI/Inbox/UpdateMailsModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..b94a4cbcd1b1be1fffcba78e2439b399975e339d --- /dev/null +++ b/enzevalos_iphone/SwiftUI/Inbox/UpdateMailsModel.swift @@ -0,0 +1,55 @@ +// +// UpdateMailsModel.swift +// enzevalos_iphone +// +// Created by Chris Offner on 29.03.21. +// Copyright © 2021 fu-berlin. All rights reserved. +// + +import Foundation + +class UpdateMailsModel: ObservableObject { + @Published var updating = false + var folderPath: String + + init(folderPath: String) { + self.folderPath = folderPath + } + + var lastUpdate: String { + var text = NSLocalizedString("Updating", comment: "updating...") + + if !updating { + let last = Date() + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale.current + dateFormatter.timeStyle = .medium + let dateString = dateFormatter.string(from: last) + text = NSLocalizedString("LastUpdate", comment: "") + " " + dateString + } + + return text + } + + func updateMails() { + guard !updating else { + return + } + + LetterboxModel + .instance + .mailHandler + .updateFolder(folderpath: folderPath) { error in + if error == nil { + self.updating = false + } + // TODO: Add error message + } + updating = true + } +} + + + + + diff --git a/enzevalos_iphone/SwiftUI/SupportingViews/CircleImage.swift b/enzevalos_iphone/SwiftUI/SupportingViews/CircleImage.swift new file mode 100644 index 0000000000000000000000000000000000000000..eda6ba3e1771f919e7e669a27153ec60061677f0 --- /dev/null +++ b/enzevalos_iphone/SwiftUI/SupportingViews/CircleImage.swift @@ -0,0 +1,27 @@ +// +// CiricleImage.swift +// enzevalos_iphone +// +// Created by Oliver Wiese on 02.03.20. +// Copyright © 2020 fu-berlin. All rights reserved. +// + +import SwiftUI + +struct CircleImage: View { + var image: Image + var radius: CGFloat = 60 + + var body: some View { + image + .resizable() + .frame(width: radius, height: radius) + .shadow(radius: radius/60*5) + } +} + +struct CircleImage_Previews: PreviewProvider { + static var previews: some View { + CircleImage(image: ProxyContact.makeImg("Alice", color: .red)) + } +} diff --git a/enzevalos_iphone/SwiftUI/SupportingViews/MailView.swift b/enzevalos_iphone/SwiftUI/SupportingViews/MailView.swift index 4b10db6ac7920358bf8873eac4cf32c9daac16ab..2ba4de7a6f72e1128e7d8df1e333f32e69b909d5 100644 --- a/enzevalos_iphone/SwiftUI/SupportingViews/MailView.swift +++ b/enzevalos_iphone/SwiftUI/SupportingViews/MailView.swift @@ -11,9 +11,8 @@ import SwiftUI struct MailView <M: DisplayMail>: View { - //TODO private let coord = AppDelegate.getAppDelegate().inboxCoordinator - - var mail: M + // TODO: private let coord = AppDelegate.getAppDelegate().inboxCoordinator + private var mail: M init(mail: M){ self.mail = mail