...
 
Commits (30)
This diff is collapsed.
......@@ -31,8 +31,12 @@ import UserNotifications
class AppDelegate: UIResponder, UIApplicationDelegate {
var newOnboarding=true
var newOnboarding = true
var newReadView = true
var inboxCoordinator: InboxCoordinator? = nil
var readViewCoordinator: ReadViewCoordinator? = nil
var window: UIWindow?
var contactStore = CNContactStore()
var mailHandler = MailHandler()
......@@ -321,6 +325,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
fatalError("No rootViewController!")
}
inboxCoordinator = InboxCoordinator(root: vc)
readViewCoordinator = ReadViewCoordinator(root: vc) // TODO: Improve Coordinators and init
guard let coord = inboxCoordinator else {
fatalError("No Coordinator")
}
......@@ -328,6 +333,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return
}
func presentReadViewController(mail: PersistentMail) {
//presentSwiftUIReadView()
guard let vc = self.window?.rootViewController as? UINavigationController else {
fatalError("No rootViewController!")
}
readViewCoordinator = ReadViewCoordinator(root: vc)
guard let coord = readViewCoordinator else {
fatalError("No Coordinator")
}
coord.pushReadView(mail: mail)
return
}
// Support for background fetch
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
......
......@@ -1792,7 +1792,7 @@ Um deine sicheren E-Mails auch auf einem anderen Gerät lesen zu können, muss d
<rect key="frame" x="0.0" y="0.0" width="414" height="32"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="w9U-eC-Koh">
<rect key="frame" x="8" y="-8.3333333333333357" width="30" height="38.333333333333336"/>
<rect key="frame" x="8" y="-8.3333333333333339" width="30" height="38.333333333333336"/>
<fontDescription key="fontDescription" type="system" weight="heavy" pointSize="21"/>
<state key="normal" title="✕">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
......@@ -1802,7 +1802,7 @@ Um deine sicheren E-Mails auch auf einem anderen Gerät lesen zu können, muss d
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" insetsLayoutMarginsFromSafeArea="NO" text="Kontakt verifizieren" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="25l-XT-PFo">
<rect key="frame" x="116.33333333333333" y="-2.3333333333333357" width="181.66666666666669" height="26.333333333333336"/>
<rect key="frame" x="116.33333333333333" y="-2.3333333333333339" width="181.66666666666669" height="26.333333333333336"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
......@@ -2221,9 +2221,9 @@ Um deine sicheren E-Mails auch auf einem anderen Gerät lesen zu können, muss d
</scene>
</scenes>
<inferredMetricsTieBreakers>
<segue reference="3Wb-uL-BB5"/>
<segue reference="td8-VW-Wrt"/>
<segue reference="DcR-GX-scc"/>
<segue reference="rhW-cI-4c4"/>
<segue reference="hSn-Um-hji"/>
<segue reference="btx-4o-o0r"/>
<segue reference="TgN-rB-esa"/>
<segue reference="Fv4-FT-5gV"/>
<segue reference="ecN-Wn-7S0"/>
......
......@@ -30,7 +30,7 @@ public protocol Contact {
}
extension Contact {
private func makeImageFromName(_ name: String) -> UIImage {
public static func makeImageFromName(_ name: String, color: UIColor) -> UIImage {
var text: NSAttributedString
var tag = String()
if name.count > 0 {
......@@ -57,7 +57,7 @@ extension Contact {
//
// Fill background of context
//
context!.setFillColor(self.getColor().cgColor)
context!.setFillColor(color.cgColor)
context!.fill(CGRect(x: 0, y: 0, width: myBounds.size.width, height: myBounds.size.height));
......@@ -81,7 +81,7 @@ extension Contact {
return UIImage(data: contact.thumbnailImageData!)!
}
}
return makeImageFromName(self.name)
return Self.makeImageFromName(self.name, color: self.getColor())
}
func getColor() -> UIColor {
......
......@@ -7,13 +7,17 @@
import Foundation
enum Rating {
case Positiv, Neutral, Negativ
}
enum SignatureState: Int16 {
case NoSignature = 0 // -> no authenticity -> no actions possible
case NoPublicKey = 1 // -> no authenticity -> Possible actions: Ask sender to send keys, import key, Would be nice to verify mails later.
case InvalidSignature = -1 // -> ERROR -> context is manipulated (either attack or MTA...)
case ValidSignature = 2
var name: String {
var loggingTag: String {
get {
switch self {
case .NoSignature:
......@@ -27,6 +31,14 @@ enum SignatureState: Int16 {
}
}
}
var rating: Rating {
switch self {
case .NoSignature, .NoPublicKey: return .Neutral
case .ValidSignature: return .Positiv
case .InvalidSignature: return .Negativ
}
}
public static let allStates = [NoSignature, NoPublicKey, InvalidSignature, ValidSignature]
}
......@@ -36,7 +48,7 @@ enum EncryptionState: Int16 {
case ValidEncryptedWithOldKey = 2
case ValidedEncryptedWithCurrentKey = 3
var name: String {
var loggingTag: String {
get {
switch self {
case .NoEncryption:
......@@ -50,6 +62,15 @@ enum EncryptionState: Int16 {
}
}
}
var rating: Rating {
switch self {
case .NoEncryption: return .Neutral
case .ValidedEncryptedWithCurrentKey, .ValidEncryptedWithOldKey: return .Positiv
case .UnableToDecrypt: return .Neutral
}
}
public static let allStates = [NoEncryption, UnableToDecrypt, ValidEncryptedWithOldKey, ValidedEncryptedWithCurrentKey]
}
......@@ -73,6 +94,8 @@ public enum CryptoScheme {
switch i {
case 0:
return CryptoScheme.PGP
case 1:
return CryptoScheme.SMIME
default:
return CryptoScheme.UNKNOWN
}
......
......@@ -894,8 +894,7 @@ class DataHandler {
if let tmpMails = finding as? [PersistentMail] {
mails = tmpMails
}
if finding == nil || finding!.count == 0 || mails.filter({ $0.folder.path == folderPath && $0.uidvalidity == myfolder.uidvalidity }).count == 0 || uid == 0 || !myfolder.uids.contains(uid) {
if finding == nil || finding!.count == 0 || mails.filter({ $0.folder.path == folderPath}).count == 0 || uid == 0 || !myfolder.uids.contains(uid) {
// create new mail object
mail = NSEntityDescription.insertNewObject(forEntityName: "PersistentMail", into: managedObjectContext) as! PersistentMail
......
......@@ -19,8 +19,76 @@
//
import UIKit
import SwiftUI
enum DialogOption {
struct DialogStruct: Dialog {
let dialogColor: Color
let title: String
let body: String
let img: Image
let messageImage: Image?
let ctaButtonTitle: String?
let ctaButtonAction: ButtonAction?
let infoButtonTitle: String?
let moreButtons: [ButtonStruct]
let dismissButtonTitle: String?
let dismissButtonAction: ButtonAction?
}
enum DialogOption: Dialog {
var dialogColor: Color {
get {
return Color(color)
}
}
var body: String {
get {
return message
}
}
var img: Image {
get{
if let image = self.messageImage {
return image
}
return Image(systemName: "exclamationmark.triangle.fill")
}
}
var ctaButtonAction: ButtonAction? {
get {
return nil
}
}
var infoButtonTitle: String? {
get {
return nil
}
}
var moreButtons: [ButtonStruct] {
get {
return []
}
}
var dismissButtonAction: ButtonAction? {
get {
return nil
}
}
case postcard
case invitationCode(code: String)
......@@ -28,17 +96,63 @@ enum DialogOption {
case invitationStep
case invitationHelp
case travelInfo
case corrupted
case couldNotDecrypt
case couldNotDecryptTravel
case encryptedBefore
var color: UIColor {
switch self {
case .postcard : return .yellow
case .postcard : return ThemeManager.unencryptedMessageColor()
case .invitationCode : return UIColor.Invitation.orange
case .invitationWelcome : return UIColor.Invitation.orange
case .invitationStep : return UIColor.Invitation.orange
case .invitationHelp : return UIColor.Invitation.orange
case .travelInfo : return UIColor.Invitation.orange
case .corrupted : return ThemeManager.troubleMessageColor()
case .couldNotDecrypt : return ThemeManager.unencryptedMessageColor()
case .couldNotDecryptTravel : return ThemeManager.unencryptedMessageColor()
case .encryptedBefore : return ThemeManager.unencryptedMessageColor()
}
}
var messageImage: Image? {
switch self {
case .postcard : return nil
case .invitationCode : return nil
case .invitationWelcome, .invitationHelp :
switch StudySettings.invitationsmode {
case .Censorship:
var images = [UIImage]()
if let sender = UIImage(named: "bg_inviation_censor_sender"), let receiver = UIImage(named: "bg_inviation_censor_receiver") {
images.append(sender)
images.append(receiver)
if let img = UIImage.animatedImage(with: images, duration: 4) {
return Image(uiImage: img)
}
}
return nil
case .PasswordEnc:
if let img = UIImage(named: "bg_inviation"){
return Image(uiImage: img)
}
return nil
case .FreeText,
.InviteMail:
if let img = UIImage(named: "postcard") {
return Image(uiImage: img)
}
return nil
}
case .invitationStep : return nil
case .travelInfo : return nil
case .corrupted : return Image(systemName: "exclamationmark.triangle.fill")
case .couldNotDecrypt : return nil
case .couldNotDecryptTravel : return nil
case .encryptedBefore : return nil
}
}
var titleImage: UIImage? {
switch self {
......@@ -62,6 +176,14 @@ enum DialogOption {
}
case .invitationStep : return nil
case .travelInfo : return nil
case .corrupted :
let icon = UIImage(systemName: "exclamationmark.triangle.fill")?
.tint(color: self.color)
return icon
case .couldNotDecrypt : return nil
case .couldNotDecryptTravel : return nil
case .encryptedBefore : return nil
}
}
......@@ -73,23 +195,33 @@ enum DialogOption {
.invitationHelp : return nil
case .invitationStep : return UIImage(named: "ic_secure_card")
case .travelInfo : return TravelHandler.planeIcon
case .corrupted : return nil
case .couldNotDecrypt : return nil
case .couldNotDecryptTravel : return nil
case .encryptedBefore : return nil
}
}
var title: String? {
var title: String {
switch self {
case .postcard : return "Welcome"
case .postcard : return NSLocalizedString("Postcard", comment: "")
case .invitationCode : return NSLocalizedString("Invitation.Code.Title", comment: "")
case .invitationWelcome,
.invitationHelp : return NSLocalizedString("Invitation.Welcome.Title", comment: "")
case .invitationStep : return NSLocalizedString("Invitation.Step.Title", comment: "")
case .travelInfo : return NSLocalizedString("TravelInfo.Title", comment: "")
case .corrupted : return NSLocalizedString("corruptedHeadline", comment: "corrupted mail")
case .couldNotDecrypt : return NSLocalizedString("couldNotDecryptHeadline", comment: "couldn't decrypt message")
case .couldNotDecryptTravel : return NSLocalizedString("couldNotDecryptTravelHeadline", comment: "couldn't decrypt message")
case .encryptedBefore : return NSLocalizedString("encryptedBeforeHeadline", comment: "encrypted by sender before")
}
}
var message: String? {
var message: String {
switch self {
case .postcard : return "Message\nMultiline and long texts are allowed, btw second button is hidden"
case .postcard : return NSLocalizedString("ReceiveInsecureInfo", comment: "")
case .invitationWelcome,
.invitationHelp :
switch StudySettings.invitationsmode {
......@@ -109,6 +241,10 @@ enum DialogOption {
}
return String(format: NSLocalizedString("Invitation.Code.Message", comment: ""), code)
case .travelInfo : return NSLocalizedString("TravelInfo.Message", comment: "")
case .corrupted : return NSLocalizedString("corruptedText", comment: "corrupted mail")
case .couldNotDecrypt : return NSLocalizedString("couldNotDecryptText", comment: "couldn't decrypt message")
case .couldNotDecryptTravel : return NSLocalizedString("couldNotDecryptTravelText", comment: "couldn't decrypt message")
case .encryptedBefore : return NSLocalizedString("encryptedBeforeText", comment: "encrypted by sender before")
}
}
......@@ -126,6 +262,10 @@ enum DialogOption {
case .invitationStep : return NSLocalizedString("Invitation.Step.CTA", comment: "")
case .invitationHelp : return NSLocalizedString("Done", comment: "")
case .travelInfo : return NSLocalizedString("TravelInfo.Remember", comment: "")
case .corrupted : return nil
case .couldNotDecrypt : return nil
case .couldNotDecryptTravel : return nil
case .encryptedBefore : return nil
}
}
......@@ -137,6 +277,10 @@ enum DialogOption {
case .invitationStep : return nil
case .invitationHelp : return nil
case .travelInfo : return NSLocalizedString("MoreInformation", comment: "")
case .corrupted : return nil
case .couldNotDecrypt : return nil
case .couldNotDecryptTravel : return nil
case .encryptedBefore : return nil
}
}
......@@ -148,6 +292,11 @@ enum DialogOption {
case .invitationStep : return NSLocalizedString("Invitation.Step.Undo", comment: "")
case .invitationHelp : return nil
case .travelInfo : return NSLocalizedString("OK", comment: "")
case .corrupted : return nil
case .couldNotDecrypt : return nil
case .couldNotDecryptTravel : return nil
case .encryptedBefore : return nil
}
}
}
......@@ -86,9 +86,7 @@ extension DialogViewController {
}
func layout(for option: DialogOption) {
self.titleLabel?.isHidden = (option.title == nil)
self.titleLabel?.text = option.title
self.messageLabel?.isHidden = (option.message == nil)
self.messageLabel?.text = option.message
self.ctaButton?.isHidden = (option.ctaButtonTitle == nil)
self.ctaButton?.setTitle(option.ctaButtonTitle, for: .normal)
......
......@@ -202,7 +202,12 @@ class FolderViewController: UITableViewController {
performSegue(withIdentifier: "showInboxSegue", sender: nil)
}
} else if sectionType(indexPath) == .mails {
performSegue(withIdentifier: "readFolderMailSegue", sender: getMails()[indexPath.row])
let mail = getMails()[indexPath.row]
if AppDelegate.getAppDelegate().newReadView, let coord = AppDelegate.getAppDelegate().readViewCoordinator {
coord.pushReadView(mail: mail)
return
}
performSegue(withIdentifier: "readFolderMailSegue", sender: mail)
} else {
let vc = storyboard?.instantiateViewController(withIdentifier: "folderViewController") as! FolderViewController
vc.folders = []
......
......@@ -20,6 +20,7 @@
import Foundation
import CoreData
import SwiftUI
extension KeyRecord {
......@@ -110,3 +111,52 @@ extension KeyRecord{
DataHandler.handler.save(during: "Fix first mails")
}
}
extension KeyRecord: DisplayContact {
var previousMails: Int {
return self.persistentMails?.count ?? 0
}
var previousResponses: Int {
return -1
}
var keyRecord: KeyRecord? {
return self
}
var addr: String {
return self.addresses.first?.mailAddress ?? "No address"
}
var myImage: Image {
return self.image.img
}
var isInContactBook: Bool {
return self.cnContact != nil
}
var otherAddresses: [String] {
return self.addressNames
}
var keyIDs: [String] {
if let id = self.keyID {
return [id]
}
return []
}
var hasPreviousMails: Bool {
return self.persistentMails?.count ?? 0 > 1
}
var hasSimilarContacts: Bool {
return false // TODO!
}
var similarContacts: [String] {
return [] // TODO!
}
}
......@@ -316,6 +316,10 @@ extension KeyViewController: UITableViewDataSource {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "firstMail" {
if let mail = record?.key?.firstMail {
if AppDelegate.getAppDelegate().newReadView, let coord = AppDelegate.getAppDelegate().readViewCoordinator {
coord.pushReadView(mail: mail)
return
}
let DestinationViewController: ReadViewController = segue.destination as! ReadViewController
DestinationViewController.mail = mail
}
......
......@@ -242,6 +242,10 @@ class ListViewController: UITableViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "readMailSegue" {
if let mail = sender as? PersistentMail {
if AppDelegate.getAppDelegate().newReadView, let coord = AppDelegate.getAppDelegate().readViewCoordinator {
coord.pushReadView(mail: mail)
return
}
let DestinationViewController: ReadViewController = segue.destination as! ReadViewController
DestinationViewController.mail = mail
}
......
......@@ -442,7 +442,7 @@ public class LogData {
}
private static func makeMailKey(enc: EncryptionState, sig: SignatureState, received: Bool) -> String {
var key = "\(enc.name)&\(sig.name)"
var key = "\(enc.loggingTag)&\(sig.loggingTag)"
if received {
key = "in|" + key
}
......
......@@ -20,6 +20,7 @@
//
import Foundation
import SwiftUI
import CoreData
extension Mail_Address {
......@@ -154,3 +155,78 @@ extension Mail_Address {
@NSManaged private func removeFromKeys(_ values: NSSet)
}
extension Mail_Address: DisplayContact {
var name: String {
if let contact = self.contact {
return contact.name
}
return self.addr
}
var addr: String {
return self.address
}
var myImage: Image {
if let contact = self.contact {
return Image(uiImage: contact.getImageOrDefault())
}
let color = UIColor(hue: CGFloat(addr.hashValue) / CGFloat(UINT32_MAX), saturation: 1, brightness: 0.75, alpha: 1)
return PseudoContact.makeImg(addr, color: color)
}
var isInContactBook: Bool {
if let con = self.contact {
return con.cnContact != nil
}
return false
}
var otherAddresses: [String] {
if let con = contact {
return con.getMailAddresses().map({$0.mailAddress})
}
return []
}
var keyIDs: [String] {
let pks = self.publicKeys.map({$0.keyID})
return pks
}
var previousMails: Int {
if let from = from {
return from.count
}
return 0
}
var previousResponses: Int {
var responses = 0
if let to = self.to {
responses += to.count
}
if let cc = self.cc {
responses += cc.count
}
if let bcc = self.bcc {
responses += bcc.count
}
return responses
}
var hasSimilarContacts: Bool {
return false
}
var similarContacts: [String] {
return []
}
var keyRecord: KeyRecord? {
return DataHandler.handler.getKeyRecord(addr: addr, keyID: self.primaryKeyID, saveRecord: false)
}
}
......@@ -417,3 +417,75 @@ extension PersistentMail {
@NSManaged public func removeFromReferenceMails(_ values: NSSet)
}
extension PersistentMail: DisplayMail {
typealias C = Mail_Address
var sender: C {
return self.from as! PersistentMail.C
}
var tos: [C] {
return self.getReceivers()
}
var ccs: [C] {
return self.getCCs()// TODO
}
var bccs: [C] {
return self.getBCCs()
}
var routingStops: [Landmark] {
return [] // TODO
}
var signedState: SignatureState {
return self.sigState
}
var persistentMail: PersistentMail? {
return self
}
var displayAttachments: [DisplayAttachment] {
var attachments = [DisplayAttachment]()
if let coreDataAttachment = self.attachments {
for att in coreDataAttachment {
if let att = att as? Attachment {
attachments.append(att)
}
}
}
return attachments
}
var folderType: FolderType {
return .Other // TODO FIX
}
func markAsRead(isRead: Bool) {
self.isRead = isRead
}
var transportEnc: Bool {
get {
return true
}
}
}
extension Attachment: DisplayAttachment {
var myName: String {
return name ?? "No name"
}
var myData: Data {
return data ?? Data()
}
}
......@@ -425,37 +425,6 @@ third-party archives.
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>The MIT License (MIT)
Copyright (c) 2016 Brightify.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>Cuckoo</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2012 Alexey Denisov
......
//
// DialogProtocols.swift
// enzevalos_iphone
//
// Created by Oliver Wiese on 12.04.20.
// Copyright © 2020 fu-berlin. All rights reserved.
//
import SwiftUI
protocol Dialog {
var dialogColor: Color { get }
var title: String { get }
var body: String { get }
var img: Image { get }
var ctaButtonTitle: String? { get }
var ctaButtonAction: ButtonAction? { get }
var infoButtonTitle: String? { get }
var moreButtons: [ButtonStruct] { get }
var dismissButtonTitle: String? { get }
var dismissButtonAction: ButtonAction? { get }
}
struct ButtonStruct {
let titleKey: String
let action: ButtonAction
let type: ButtonType
var title: String {
get {
return NSLocalizedString(titleKey, comment: "")
}
}
}
enum ButtonType {
case CTA, DISMISS, MORE
}
enum ButtonAction {
case AskSenderToConfirm, AskSenderToSendPK, AskSenderToResendForPK, InvitePerson, IgnoreMail, AskUserToImportSK, ImportSK, ImportPK, MoreInformation, ExportSK, OK, IgnoreWarning, SendPK
var title: LocalizedStringKey {
get {
switch self {
case .AskSenderToConfirm: return "Security.Dialog.Button.Title.Confirmation"
case .AskSenderToSendPK: return "Security.Dialog.Button.Title.Ask.ForPK"
case .AskSenderToResendForPK: return "Security.Dialog.Button.Title.Ask.Resend.ForPK"
case .InvitePerson: return "Security.Dialog.Button.Title.Invite"
case .IgnoreMail: return "Security.Dialog.Button.Title.Ignore.Mail"
case .AskUserToImportSK: return "MISSING"
case .ImportSK: return "Security.Dialog.Button.Title.Import.SK"
case .ImportPK: return "MISSING"
case .MoreInformation: return "MoreInformation"
case .ExportSK: return "MISSING"
case .OK: return "Security.Dialog.Button.Title.OK"
case .IgnoreWarning: return "MISSING"
case .SendPK: return "Security.Dialog.Button.Title.Send.PK"
}
}
}
var color: Color {
get {
let defaultColor = Color.blue
switch self {
case .AskSenderToConfirm: return defaultColor
case .AskSenderToSendPK: return defaultColor
case .AskSenderToResendForPK: return defaultColor
case .InvitePerson: return defaultColor
case .IgnoreMail: return defaultColor
case .AskUserToImportSK: return defaultColor
case .ImportSK: return defaultColor
case .ImportPK: return defaultColor
case .MoreInformation: return defaultColor
case .ExportSK: return defaultColor
case .OK: return defaultColor
case .IgnoreWarning: return defaultColor
case .SendPK: return defaultColor
}
}
}
}
This diff is collapsed.
This diff is collapsed.
......@@ -69,7 +69,7 @@ struct Inbox: View {
private var composeButton: some View {
Button(action: self.coord.pushComposeView, label: {
Image(systemName: "message").imageScale(.large)
Image(systemName: "square.and.pencil").imageScale(.large)
})
}
......
......@@ -56,12 +56,16 @@ class InboxCoordinator {
}
func pushReadView(mail: PersistentMail) {
let vc = mainStoryboard.instantiateViewController(withIdentifier: ViewID.ReadView.rawValue)
if let vc = vc as? ReadViewController {
vc.mail = mail
if AppDelegate.getAppDelegate().newReadView, let readCoord = AppDelegate.getAppDelegate().readViewCoordinator {
readCoord.pushReadView(mail: mail)
} else {
let vc = mainStoryboard.instantiateViewController(withIdentifier: ViewID.ReadView.rawValue)
if let vc = vc as? ReadViewController {
vc.mail = mail
}
root.isToolbarHidden = false
root.pushViewController(vc, animated: true)
}
root.isToolbarHidden = false
root.pushViewController(vc, animated: true)
}
func pushRecordView(record: KeyRecord){
......
......@@ -45,21 +45,24 @@ struct KeyRecordRow: View {
*/
private func mailListView() -> AnyView {
let spacing = CGFloat(10)
let offset = -(spacing / 2)
if let first = self.first {
if let second = self.second {
return AnyView(VStack(alignment: .leading, spacing: spacing) {
MailView(mail: first)
Divider()
MailView(mail: second)
.background(Stroke(offsetY: offset))
})
}
.padding(.top, 10))
} else {
return AnyView(VStack(alignment: .leading,spacing: spacing) {
MailView(mail: first)
Divider()
Text(NSLocalizedString("NoFurtherMessages", comment: "No more Mails"))
.background(Stroke(offsetY: offset))
})
}
.padding(.top, 10))
}
} else {
return AnyView(Text("NO MAILS..."))
......
//
// ReadMainView.swift
// enzevalos_iphone
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import SwiftUI
struct ReadMainView <M: DisplayMail>: View {
public var mail: M
var body: some View {
TabView{
ForEach(Tabs, id: \.id ){ tab in
tab.content
.tabItem {
tab.image
Text(tab.description)
}
}
}
}
//TODO: not use AnyView-workaround
var Tabs:[Tab] {
get {
return [
Tab(
image: Image(systemName: "shield"),
description: "security",
content:AnyView(SecurityBriefingView(mail: mail))
),
Tab(
image: Image(systemName: "person.fill"),
description: "sender",
content: AnyView(SenderViewMain<M>(mail: mail))
),
Tab(
image: Image(systemName: "text.bubble.fill"),
description: "message",
content: AnyView(MessageViewMain<M>(mail: mail))
),
Tab(
image: Image(systemName: "rectangle.and.paperclip"),
description: "attachments",
content: AnyView(AttachmentsViewMain(attachments: mail.displayAttachments, dlAll: true))
)
]
}
}
}
struct Tab {
let id = UUID()
var image : Image = Image(systemName: "tag.fill")
var description : String = "---"
var content : AnyView = AnyView(Text("Still in development"))
}
#if DEBUG
struct Layout_Previews: PreviewProvider {
static let simulator = Simulators<ReadMainView<PseuoMail>>()
static let deviceNames: [String] = [
"iPhone SE",
"iPhone 11 Pro Max"
]
static var previews: some View {
simulator.previews(view: ReadMainView(mail: DummyData.SecureMail))
}
}
#endif
//
// ReadViewCoordinator.swift
// enzevalos_iphone
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import Foundation
import SwiftUI
/*
TODOS:
* Does Views disappear?
*/
class ReadViewCoordinator {
let root: UINavigationController
private let mainStoryboardName = "Main"
private let mainStoryboard: UIStoryboard
private var readView: UIViewController?
private var mail: PersistentMail?
func makeAction(action: ButtonAction) {
guard let mail = mail else {
print("No mail!")
return
}
switch action {
case .AskSenderToConfirm: askSenderToConfirm(mail: mail)
case .AskSenderToSendPK: askSenderToSendPK(mail: mail)
case .AskSenderToResendForPK: askSenderToResendForPK(mail: mail)
case .InvitePerson: invitePerson(mail: mail)
case .IgnoreMail: ignoreMail(mail: mail)
case .AskUserToImportSK: break
case .ImportSK: break
case .ImportPK: break
case .MoreInformation: break
case .ExportSK: break
case .OK: break
case .IgnoreWarning: break
case .SendPK: break
}
}
init(root: UINavigationController) {
self.root = root
mainStoryboard = UIStoryboard(name: self.mainStoryboardName, bundle: nil)
}
func pushReadView(mail: PersistentMail){
self.mail = mail
root.isToolbarHidden = true
try? AppDelegate.getAppDelegate().mailHandler.startIMAPIdleIfSupported()
AppDelegate.getAppDelegate().mailHandler.updateFolder(folder: Folder.inbox, completionCallback: {_ in
})
let icon = mail.titleIcon
let view = UIImageView(image: icon)
let vc = UIHostingController(rootView: ReadMainView(mail: mail))
readView = vc
vc.navigationItem.titleView = view
root.pushViewController(vc, animated: true)
}
func delete(mail: PersistentMail){
Logger.log(delete: mail, toTrash: true)
AppDelegate.getAppDelegate().mailHandler.move(mails: [mail], from: mail.folder.path, to: UserManager.backendTrashFolderPath)
AppDelegate.getAppDelegate().presentInboxViewController()
}
func archive(mail: PersistentMail){
Logger.log(archive: mail)
AppDelegate.getAppDelegate().mailHandler.move(mails: [mail], from: mail.folder.path, to: UserManager.backendArchiveFolderPath)
}
func pushComposeView(to: [MailAddress], cc: [MailAddress], bcc: [MailAddress], subject: String?, body: String?, responseType: ResponseType?) {
let vc = mainStoryboard.instantiateViewController(identifier: ViewID.ComposeView.rawValue)
var prefilledMail: EphemeralMail?
if let subject = subject, let body = body, let responseType = responseType {
prefilledMail = EphemeralMail(to: NSSet.init(array: to), cc: NSSet.init(array: cc), bcc: NSSet.init(array: bcc), date: Date(), subject: responseType.addPrefix(subject: subject), body: body, uid: 0, predecessor: nil)
}
if let vc = vc as? SendViewController {
vc.wasPushed = true
vc.prefilledMail = prefilledMail
}
root.isToolbarHidden = false
root.pushViewController(vc, animated: true)
}
func pushContactView(contact: KeyRecord) {
let vc = mainStoryboard.instantiateViewController(withIdentifier: ViewID.KeyRecordView.rawValue)
if let vc = vc as? ContactViewController {
vc.keyRecord = contact
}
root.isToolbarHidden = false
root.pushViewController(vc, animated: true)
}
func shareData(_ data:NSData){
let vc = UIActivityViewController(activityItems: [data], applicationActivities: [])
root.present(vc, animated: true)
}
/// AskUserToImportSK, ImportSK, ImportPK, MoreInformation, ExportSK, OK, IgnoreWarning, SendPK
func ignoreMail <M: DisplayMail> (mail: M) {
guard let m = mail.persistentMail else {
return
}
delete(mail: m)
}
func invitePerson <M: DisplayMail> (mail: M) {
let body = String(format: NSLocalizedString("inviteText", comment: "Body for the invitation mail"),StudySettings.studyID)
let subject = NSLocalizedString("inviteSubject", comment: "Subject for the invitation mail")
var to = [MailAddress] ()
to.append(mail.sender.findAddress(temp: false))
to.append(contentsOf: findPersisentContact(persons: mail.ccs))
to.append(contentsOf: findPersisentContact(persons: mail.tos))
to = to.filter({!$0.hasKey})
pushComposeView(to: to, cc: [], bcc: [], subject: subject, body: body, responseType: .Reply)
}
func askSenderToResendForPK <M: DisplayMail> (mail: M) {
guard let sender = mail.sender.keyRecord else {
return
}
let to = [sender.findAddress(temp: false)]
var body = NSLocalizedString("ReadView.PrefilledMail.AskForResend.Body", comment: "")
body += preparePreviousMailBody(mail: mail)
pushComposeView(to: to, cc: [], bcc: [], subject: mail.subject, body: body, responseType: .Reply)
// TODO: Add Public key!
}
func askSenderToSendPK <M: DisplayMail>(mail: M) {
guard let sender = mail.sender.keyRecord else {
return
}
let to = [sender.findAddress(temp: false)]
let body = NSLocalizedString("ReadView.PrefilledMail.AskForPK.Body", comment: "")
let subject = NSLocalizedString("ReadView.PrefilledMail.AskForPK.Subject", comment: "")
pushComposeView(to: to, cc: [], bcc: [], subject: subject, body: body, responseType: .none)
}
func askSenderToConfirm <M: DisplayMail>(mail: M) {
let to = [mail.sender.findAddress(temp: false)]
print(mail.sender.findAddress(temp: false).mailAddress)
let body = NSLocalizedString("didYouSendThis", comment: "Did you sent this mail?") + "\n"+preparePreviousMailBody(mail: mail)
pushComposeView(to: to, cc: [], bcc: [], subject: mail.subject, body: body, responseType: .Reply)
}
private func preparePreviousMailBody<M: DisplayMail>(mail: M) -> String{
var body = NSLocalizedString("mail from", comment: "describing who send the mail") + " "
body.append(mail.sender.addr)
let time = DateFormatter.init()
time.dateStyle = .short
time.timeStyle = .short
time.locale = Locale.current
body.append(" " + NSLocalizedString("sent at", comment: "describing when the mail was send") + " " + time.string(from: mail.date))
body.append("\n" + NSLocalizedString("To", comment: "describing adressee") + ": ")
for addr in mail.tos {
body.append("\(addr.addr), ")
}
if mail.ccs.count > 0 {
body.append("\n\(NSLocalizedString("Cc", comment: "")): ")
for addr in mail.ccs {
body.append("\(addr.addr), ") }
}
body.append("\n" + NSLocalizedString("subject", comment: "describing what subject was choosen") + ": " + (mail.subject ?? ""))
body.append("\n------------------------\n\n" + (mail.body ?? ""))
body = body.components(separatedBy: "\n").map { line in
if line.hasPrefix(">") {
return ">" + line
}
return "> " + line
}.reduce("", { $0 + "\n" + $1 })
return body
}
private func findPersisentContact (persons: [DisplayContact]) -> [MailAddress] {
var result = [MailAddress] ()
for p in persons {
if let contact = p.keyRecord {
result.append(contact.findAddress(temp: true))
}
// TODO: Add data handler?
}
return result
}
}
//
// AttPreview.swift
// enzevalos_iphone
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import SwiftUI
import QuickLook
//To use the QuickLook in SwiftUI
struct QuickLookView: UIViewControllerRepresentable {
var name: String
var data: Data?
//if it should download the data as a file if not yet downloaded
var shallDL: Bool = true
var allowScaling: Bool = true
func makeCoordinator() -> QuickLookView.Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> QLPreviewController {
let controller = QLPreviewController()
//the coordinator provides the real preview data as a NSURL
controller.dataSource = context.coordinator
return controller
}
func updateUIViewController(_ controller: QLPreviewController,
context: Context) {
print("updated")
controller.reloadData()//not beautiful but works
}
class Coordinator: NSObject, QLPreviewControllerDataSource {
let parent: QuickLookView
init(_ parent: QuickLookView) {
self.parent = parent
super.init()
}
// The QLPreviewController asks its delegate how many items it has:
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
// For each item (see method above), the QLPreviewController asks for
// a QLPreviewItem instance describing that item:
func previewController(
_ controller: QLPreviewController,
previewItemAt index: Int
) -> QLPreviewItem {
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
let str = parent.data!
//This is where the file is stored
let filename = getDocumentsDirectory().appendingPathComponent(parent.name)
if parent.shallDL{
do {
try str.write(to: filename)
} catch {
// failed to write file – bad permissions, bad filename, missing permissions
}
}
_ = parent.name.components(separatedBy: ".")
let path = filename
return path as NSURL
}
}
}
//
// CardWithTitle.swift
// enzevalos_iphone
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import SwiftUI
// MARK: a CardView with title
struct CardV<Content:View>: View{
var elevation:CGFloat = 3
var title:String?
var actions:[AnyView]?
var actionsVertical:Bool=false
var content: () -> Content
@State private var isAdvancedVisable:Bool = false
@State private var isTapDownB:Bool = false
private let colo=Color(red:0.85,green:0.85,blue:0.88)
var body: some View{
ZStack {
VStack{
VStack{
content()
.background(Color.white)
.clipShape(
RoundedRectangle(cornerRadius: 15)
)
.padding(3)
.opacity(isTapDownB ? 0.7 : 1)
if title != nil {
Text(title!).padding(7)
}
}
.background(isTapDownB ? colo : Color.white)
.clipShape(
RoundedRectangle(cornerRadius: 15)
)
.gesture (
TapGesture()
.onEnded{ _ in
self.isTapDownB.toggle()
self.isAdvancedVisable.toggle()
}
)
if isAdvancedVisable{
Advanced.padding(.horizontal,10)
}
}
.shadow(color: isTapDownB ? Color.clear : colo , radius: 2*elevation, x: elevation, y:elevation)
}
.padding(.leading, 10)
.padding(2*elevation)
.padding(.bottom,isTapDownB ? elevation : 2*elevation)
.animation(.easeIn(duration: 0.2))
.scaleEffect(isTapDownB ? 0.82 : 1)
.opacity(isTapDownB ? 0.9 : 1)
}
private func Actions(_ nr:Int)-> some View{
self.actions![nr]
.padding(10)
.background(Color.white)
.clipShape(
RoundedRectangle(cornerRadius: 15)
)
.shadow(color: colo , radius: 2*elevation, x: elevation, y:elevation)
}
//the button section beneath
private var Advanced: AnyView{
if actions != nil{
if actionsVertical{
return AnyView(VStack{
ForEach(0..<actions!.count){ i in
self.Actions(i)
}
})
}else{
return AnyView(HStack{
ForEach(0..<actions!.count){ i in
self.Actions(i)
}
})
}
}
return AnyView(EmptyView())
}
}