浏览代码

偏好设置-新增保存密码交互

tangchao 1 年之前
父节点
当前提交
0824b5e6c6

+ 18 - 0
PDF Office/PDF Office.xcodeproj/project.pbxproj

@@ -2435,6 +2435,9 @@
 		BBA93D2D29BEBAA60044E0DD /* KMPreferenceEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA93D2C29BEBAA60044E0DD /* KMPreferenceEnum.swift */; };
 		BBA93D2E29BEBAA60044E0DD /* KMPreferenceEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA93D2C29BEBAA60044E0DD /* KMPreferenceEnum.swift */; };
 		BBA93D2F29BEBAA60044E0DD /* KMPreferenceEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA93D2C29BEBAA60044E0DD /* KMPreferenceEnum.swift */; };
+		BBA93D3329BF0ECE0044E0DD /* SKKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = BBA93D3229BF0ECD0044E0DD /* SKKeychain.m */; };
+		BBA93D3429BF0ECE0044E0DD /* SKKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = BBA93D3229BF0ECD0044E0DD /* SKKeychain.m */; };
+		BBA93D3529BF0ECE0044E0DD /* SKKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = BBA93D3229BF0ECD0044E0DD /* SKKeychain.m */; };
 		BBAFC83C2985194800D0648E /* KMPDFEditAppendWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = BBAFC8392985194800D0648E /* KMPDFEditAppendWindow.m */; };
 		BBAFC83D2985194800D0648E /* KMPDFEditAppendWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = BBAFC8392985194800D0648E /* KMPDFEditAppendWindow.m */; };
 		BBAFC83E2985194800D0648E /* KMPDFEditAppendWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = BBAFC8392985194800D0648E /* KMPDFEditAppendWindow.m */; };
@@ -4070,6 +4073,8 @@
 		BBA8B7AE293600D70097D183 /* KMPasswordInputWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPasswordInputWindow.swift; sourceTree = "<group>"; };
 		BBA8B7B2293635D80097D183 /* KMPasswordInputWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = KMPasswordInputWindow.xib; sourceTree = "<group>"; };
 		BBA93D2C29BEBAA60044E0DD /* KMPreferenceEnum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPreferenceEnum.swift; sourceTree = "<group>"; };
+		BBA93D3129BF0ECD0044E0DD /* SKKeychain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SKKeychain.h; sourceTree = "<group>"; };
+		BBA93D3229BF0ECD0044E0DD /* SKKeychain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SKKeychain.m; sourceTree = "<group>"; };
 		BBAFC8392985194800D0648E /* KMPDFEditAppendWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMPDFEditAppendWindow.m; sourceTree = "<group>"; };
 		BBAFC83A2985194800D0648E /* KMPDFEditAppendWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMPDFEditAppendWindow.h; sourceTree = "<group>"; };
 		BBAFC83B2985194800D0648E /* KMPDFEditAppendWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = KMPDFEditAppendWindow.xib; sourceTree = "<group>"; };
@@ -7053,6 +7058,7 @@
 		BB5F8A0329BB04EF00365ADB /* OC */ = {
 			isa = PBXGroup;
 			children = (
+				BBA93D3029BF0ECD0044E0DD /* KeyChain */,
 				BB9695A529BDA3A800FD68D3 /* Property */,
 				BB5F8A1429BB15AD00365ADB /* EmailSubscription */,
 				BB5F8A0429BB04EF00365ADB /* GBDeviceInfo */,
@@ -7492,6 +7498,15 @@
 			path = Window;
 			sourceTree = "<group>";
 		};
+		BBA93D3029BF0ECD0044E0DD /* KeyChain */ = {
+			isa = PBXGroup;
+			children = (
+				BBA93D3129BF0ECD0044E0DD /* SKKeychain.h */,
+				BBA93D3229BF0ECD0044E0DD /* SKKeychain.m */,
+			);
+			path = KeyChain;
+			sourceTree = "<group>";
+		};
 		BBAFC8382985191200D0648E /* OCPart */ = {
 			isa = PBXGroup;
 			children = (
@@ -9606,6 +9621,7 @@
 				BB8F457E295B00130037EA22 /* KMWatermarkAdjectiveTools.swift in Sources */,
 				BB332D532995D8B500CABB58 /* KMCloudDocumentTools.swift in Sources */,
 				89316837296E436B0073EA59 /* NSImage+CustomImage.m in Sources */,
+				BBA93D3329BF0ECE0044E0DD /* SKKeychain.m in Sources */,
 				9F0CB5152986566D00007028 /* KMDesignToken+BorderRadiusTopLeft.swift in Sources */,
 				9F0CB535298656F900007028 /* KMDesignToken+BorderWidthLeft.swift in Sources */,
 				ADD1B7022946C8AD00C3FFF7 /* KMBaseTextField.swift in Sources */,
@@ -10324,6 +10340,7 @@
 				BB8F457F295B00130037EA22 /* KMWatermarkAdjectiveTools.swift in Sources */,
 				BB332D542995D8B500CABB58 /* KMCloudDocumentTools.swift in Sources */,
 				89316838296E436B0073EA59 /* NSImage+CustomImage.m in Sources */,
+				BBA93D3429BF0ECE0044E0DD /* SKKeychain.m in Sources */,
 				9F0CB5162986566D00007028 /* KMDesignToken+BorderRadiusTopLeft.swift in Sources */,
 				9F0CB536298656F900007028 /* KMDesignToken+BorderWidthLeft.swift in Sources */,
 				BBA8B7AB2935DC120097D183 /* KMRemovePasswordResultTipView.swift in Sources */,
@@ -11277,6 +11294,7 @@
 				AD88109829A78ADC00178CA1 /* KMVerificationCodeView.swift in Sources */,
 				9F0CB4FF298655F600007028 /* KMDesignToken+BorderColor.swift in Sources */,
 				AD71E7C62990A8A4001C5D3F /* KMBatchRemoveViewModel.swift in Sources */,
+				BBA93D3529BF0ECE0044E0DD /* SKKeychain.m in Sources */,
 				89E4E71F2963F77C002DBA6F /* KMSelfSignAnnotationFreeText.m in Sources */,
 				9F1F82C5292F113A0092C4B4 /* KMHomeDragView.swift in Sources */,
 				AD4B45732940B0000076FA14 /* KMOCRModel.swift in Sources */,

二进制
PDF Office/PDF Office.xcodeproj/project.xcworkspace/xcuserdata/kdanmobile.xcuserdatad/UserInterfaceState.xcuserstate


+ 53 - 0
PDF Office/PDF Office/Class/Common/OC/KeyChain/SKKeychain.h

@@ -0,0 +1,53 @@
+//
+//  SKKeychain.h
+//  Skim
+//
+//  Created by Christiaan on 29/01/2018.
+/*
+ This software is Copyright (c) 2018
+Christiaan Hofman. All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ 
+ - Neither the name of Christiaan Hofman nor the names of any
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+
+typedef NS_ENUM(NSInteger, SKPasswordStatus) {
+    SKPasswordStatusFound,
+    SKPasswordStatusNotFound,
+    SKPasswordStatusError
+};
+
+@interface SKKeychain : NSObject
+
++ (SKPasswordStatus)getPassword:(NSString **)password item:(id *)itemPtr forService:(NSString *)service account:(NSString *)account;
++ (void)setPassword:(NSString *)password item:(id)item forService:(NSString *)service account:(NSString *)account label:(NSString *)label comment:(NSString *)comment;
+
+@end

+ 109 - 0
PDF Office/PDF Office/Class/Common/OC/KeyChain/SKKeychain.m

@@ -0,0 +1,109 @@
+//
+//  SKKeychain.m
+//  Skim
+//
+//  Created by Christiaan on 29/01/2018.
+/*
+ This software is Copyright (c) 2018
+ Christiaan Hofman. All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ 
+ - Neither the name of Christiaan Hofman nor the names of any
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "SKKeychain.h"
+#import <Security/Security.h>
+
+
+@implementation SKKeychain
+
++ (SKPasswordStatus)getPassword:(NSString **)password item:(id *)itemPtr forService:(NSString *)service account:(NSString *)account {
+    void *passwordData = NULL;
+    UInt32 passwordLength = 0;
+    const char *serviceData = [service UTF8String];
+    const char *accountData = [account UTF8String];
+    OSStatus err = SecKeychainFindGenericPassword(NULL, strlen(serviceData), serviceData, strlen(accountData), accountData, password ? &passwordLength : NULL, password ? &passwordData : NULL, NULL);
+    
+    if (err == noErr) {
+        if (password) {
+            *password = [[NSString alloc] initWithBytes:passwordData length:passwordLength encoding:NSUTF8StringEncoding];
+            SecKeychainItemFreeContent(NULL, passwordData);
+        }
+//        if (itemPtr)
+//            [*itemPtr autorelease];
+        return SKPasswordStatusFound;
+    } else if (err == errSecItemNotFound) {
+        return SKPasswordStatusNotFound;
+    } else {
+        if (err != errSecUserCanceled)
+            NSLog(@"Error %d occurred finding password: %@", (int)err, SecCopyErrorMessageString(err, NULL));
+        return SKPasswordStatusError;
+    }
+}
+
+static inline SecKeychainAttribute makeKeychainAttribute(SecKeychainAttrType tag, NSString *string) {
+    const char *data = [string UTF8String];
+    SecKeychainAttribute attr;
+    attr.tag = tag;
+    attr.length = strlen(data);
+    attr.data = (void *)data;
+    return attr;
+}
+
++ (void)setPassword:(NSString *)password item:(id)item forService:(NSString *)service account:(NSString *)account label:(NSString *)label comment:(NSString *)comment {
+    const void *passwordData = [password UTF8String];
+    UInt32 passwordLength = password ? strlen(passwordData) : 0;
+    NSUInteger attrCount = 2;
+    SecKeychainAttributeList attributes;
+    SecKeychainAttribute attrs[4];
+    OSStatus err;
+    
+    attrs[0] = makeKeychainAttribute(kSecServiceItemAttr, service);
+    attrs[1] = makeKeychainAttribute(kSecAccountItemAttr, account);
+    if (label)
+        attrs[attrCount++] = makeKeychainAttribute(kSecLabelItemAttr, label);
+    if (comment)
+        attrs[attrCount++] = makeKeychainAttribute(kSecCommentItemAttr, comment);
+    
+    attributes.count = attrCount;
+    attributes.attr = attrs;
+    
+    if (item) {
+        // password was on keychain, so modify the keychain
+        err = SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)CFBridgingRetain(item), &attributes, passwordLength, passwordData);
+        if (err != noErr && err != errSecUserCanceled)
+            NSLog(@"Error %d occurred modifying password: %@", (int)err, SecCopyErrorMessageString(err, NULL));
+    } else if (password) {
+        // password not on keychain, so add it
+        err = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &attributes, passwordLength, passwordData, NULL, NULL, NULL);
+        if (err != noErr && err != errSecUserCanceled)
+            NSLog(@"Error %d occurred adding password: %@", (int)err, SecCopyErrorMessageString(err, NULL));
+    }
+}
+
+@end

+ 64 - 1
PDF Office/PDF Office/Class/Document/KMMainDocument.swift

@@ -16,6 +16,7 @@ typealias KMMainDocumentCloudUploadHanddler = (@escaping(Bool, String)->()) -> (
     var closedByUserGestureFlag: Bool = false   // 标记 closedByUserGesture 这个状态需要延后存储(如果需要)
     var cloud: Bool = false
     var cloudUploadHanddler: KMMainDocumentCloudUploadHanddler?
+    var isUnlockFromKeychain: Bool = false
     
     override func makeWindowControllers() {
         // Returns the storyboard that contains your document window.
@@ -120,11 +121,13 @@ typealias KMMainDocumentCloudUploadHanddler = (@escaping(Bool, String)->()) -> (
     override func showWindows() {
         super.showWindows()
         
+        self.setDataFromTmpData()
     }
     
     override func windowControllerDidLoadNib(_ aController: NSWindowController) {
         super.windowControllerDidLoadNib(aController)
-        // Add any code here that needs to be executed once the windowController has loaded the document's window.
+        
+        self.setDataFromTmpData()
     }
     
 //    override func save(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType) async throws {
@@ -255,6 +258,30 @@ typealias KMMainDocumentCloudUploadHanddler = (@escaping(Bool, String)->()) -> (
     func isPDFDocument() -> Bool {
         return true
     }
+    
+    func setDataFromTmpData() {
+        let document = self.mainViewController.document
+        if (document == nil) {
+            return
+        }
+        
+        self.tryToUnlockDocument(document!)
+    }
+    
+    func tryToUnlockDocument(_ document: CPDFDocument) {
+        if (document.permissionsStatus != .owner) {
+            var password: NSString? = nil
+            let fileId = self.fileId(for: document)
+            if (fileId.isEmpty) {
+                return
+            }
+            self.getPassword(&password, fileId: fileId)
+            if (password != nil) {
+                self.isUnlockFromKeychain = true
+                document.unlock(withPassword: password! as String)
+            }
+        }
+    }
 }
 
 extension NSDocument {
@@ -342,4 +369,40 @@ extension NSDocument {
         alert.runModal()
         return true
     }
+    
+
+}
+
+// MARK: -
+// MARK: 保存密码
+extension NSDocument {
+    func savePasswordInKeychain(_ password: String, _ document: CPDFDocument) {
+        if (document.isLocked || password.isEmpty) {
+            return
+        }
+        
+        let fileId = self.fileId(for: document)
+        if (fileId.isEmpty) {
+            return
+        }
+        
+//        let status: SKPasswordStatus =
+        let label = "PDF Office: \(self.displayName!)"
+        SKKeychain.setPassword(password, item: nil, forService: self.passwordServiceName(), account: fileId, label: label, comment: self.fileURL?.path)
+    }
+    
+    func getPassword(_ password: AutoreleasingUnsafeMutablePointer<NSString?>, fileId: String) {
+        let status = SKKeychain.getPassword(password, item: nil, forService: self.passwordServiceName(), account: fileId)
+//        if (status == .found) {
+            
+//        }
+    }
+    
+    fileprivate func fileId(for document: CPDFDocument) -> String {
+        return "\(document.documentURL.path.hash)"
+    }
+    
+    private func passwordServiceName() -> String {
+        return "PDF Office password"
+    }
 }

+ 36 - 0
PDF Office/PDF Office/Class/PDFWindowController/ViewController/KMMainViewController.swift

@@ -169,6 +169,7 @@ import Cocoa
         NotificationCenter.default.addObserver(self, selector: #selector(rename(_:)), name: NSNotification.Name.init(rawValue: "KMTabControllerRename"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(showInFinder(_:)), name: NSNotification.Name.init(rawValue: "KMTabControllerShowInFinder"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(preferenceDidChangeNotification), name: Notification.Name(KMPreferenceDidChangeNotificationName), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(documentDidUnlockNotification), name: Notification.Name("CPDFDocumentDidUnlockNotification"), object: nil)
     }
         
     //MARK: - PDFListView
@@ -556,6 +557,41 @@ import Cocoa
         document?.save(nil)
     }
     
+    // MARK: -
+    // MARK: Noti Actions
+    
+    internal func documentDidUnlockNotification(_ sender: Notification) {
+        if (self.listView.document.isEqual(to: sender.object)) {
+            
+            if (self.myDocument == nil) {
+                return
+            }
+            if ((self.myDocument as! KMMainDocument).isUnlockFromKeychain) {
+                return
+            }
+            
+            let type = KMPreferenceManager.shared.savePasswordType
+            if (type == .never) {
+                return
+            }
+            if (type == .always) {
+                self.myDocument?.savePasswordInKeychain(self.listView.document.password, self.listView.document)
+                return
+            }
+            
+            // 保存到钥匙串
+            let alert = NSAlert()
+            alert.messageText = NSLocalizedString("Remember Password?", comment: "")
+            alert.informativeText = NSLocalizedString("Do you want to save this password in your Keychain?", comment: "")
+            alert.addButton(withTitle: NSLocalizedString("Yes", comment: ""))
+            alert.addButton(withTitle: NSLocalizedString("No", comment: ""))
+            if (alert.runModal() == .alertFirstButtonReturn) { // 保存密码
+                self.myDocument?.savePasswordInKeychain(self.listView.document.password, self.listView.document)
+                return
+            }
+        }
+    }
+    
     //MARK: - PDFListViewDelegate
     func pdfViewDocumentDidLoaded(_ pdfView: CPDFView!) {
 #if DEBUG

+ 6 - 6
PDF Office/PDF Office/Class/Preference/Controller/KMPreferenceGeneralController.swift

@@ -183,11 +183,11 @@ class KMPreferenceGeneralController: NSViewController {
         }
         
         let value  = KMPreferenceManager.shared.savePasswordType
-        if (value == 0) {
+        if (value == .always) {
             self.selectSavePasswordRadio(self.savePasswordAlwaysRadio)
-        } else if (value == 1) {
+        } else if (value == .never) {
             self.selectSavePasswordRadio(self.savePasswordNeverRadio)
-        } else if (value == 2) {
+        } else if (value == .ask) {
             self.selectSavePasswordRadio(self.savePasswordAskRadio)
         }
         
@@ -215,15 +215,15 @@ class KMPreferenceGeneralController: NSViewController {
         } else if (sender.isEqual(to: self.savePasswordAlwaysRadio)) {
             self.selectSavePasswordRadio(sender)
             
-            KMPreferenceManager.shared.savePasswordType = 0
+            KMPreferenceManager.shared.savePasswordType = .always
         } else if (sender.isEqual(to: self.savePasswordNeverRadio)) {
             self.selectSavePasswordRadio(sender)
             
-            KMPreferenceManager.shared.savePasswordType = 1
+            KMPreferenceManager.shared.savePasswordType = .never
         } else if (sender.isEqual(to: self.savePasswordAskRadio)) {
             self.selectSavePasswordRadio(sender)
             
-            KMPreferenceManager.shared.savePasswordType = 2
+            KMPreferenceManager.shared.savePasswordType = .ask
         }
     }
     

+ 6 - 0
PDF Office/PDF Office/Class/Preference/Tools/KMPreferenceEnum.swift

@@ -11,3 +11,9 @@ enum KMPreferenceCloseFilePromptType: Int {
     case promp = 0
     case noPromp = 1
 }
+
+enum KMPreferenceSavePasswordType: Int {
+    case always = 0
+    case never = 1
+    case ask = 2
+}

+ 15 - 3
PDF Office/PDF Office/Class/Preference/Tools/KMPreferenceManager.swift

@@ -517,12 +517,24 @@ extension KMPreferenceManager {
         }
     }
     
-    var savePasswordType: Int {
+    var savePasswordType: KMPreferenceSavePasswordType {
         get {
-            return self.getData(forKey: KMSavePasswordTypeKey) as! Int
+            let type: Int? = self.getData(forKey: KMSavePasswordTypeKey) as? Int
+            if (type == nil || type! == 2) {
+                return .ask
+            }
+            if (type! == 0) {
+                return .always
+            } else if (type! == 1) {
+                return .never
+            }
+            
+            return .ask
         }
         set {
-            let _ = self.setData(data: newValue, forKey: KMSavePasswordTypeKey)
+            if (newValue == .always || newValue == .never || newValue == .ask) {
+                let _ = self.setData(data: newValue.rawValue, forKey: KMSavePasswordTypeKey)
+            }
         }
     }
 }

+ 1 - 0
PDF Office/PDF Office/PDF Office DMG-Bridging-Header.h

@@ -50,3 +50,4 @@
 #import "GBDeviceInfo.h"
 #import "KMEmailSubWindowController.h"
 #import "SKInfoWindowController.h"
+#import "SKKeychain.h"

+ 1 - 0
PDF Office/PDF Office/PDF Office Pro-Bridging-Header.h

@@ -50,3 +50,4 @@
 #import "GBDeviceInfo.h"
 #import "KMEmailSubWindowController.h"
 #import "SKInfoWindowController.h"
+#import "SKKeychain.h"

+ 1 - 0
PDF Office/PDF Office/PDF Office-Bridging-Header.h

@@ -71,3 +71,4 @@
 #import "GBDeviceInfo.h"
 #import "KMEmailSubWindowController.h"
 #import "SKInfoWindowController.h"
+#import "SKKeychain.h"

+ 3 - 0
PDF Office/PDF Office/Strings/en.lproj/Localizable.strings

@@ -429,6 +429,9 @@
 "Circle border:" = "Circle border:";
 "Markup Default Font" = "Markup Default Font";
 
+"Remember Password?" = "Remember Password?";
+"Do you want to save this password in your Keychain?" = "Do you want to save this password in your Keychain?";
+
 //MARK: - 轻会员
 //界面
 "Login" = "Login";