diff --git a/Bark/AppDelegate.swift b/Bark/AppDelegate.swift index f28a138..1373dca 100644 --- a/Bark/AppDelegate.swift +++ b/Bark/AppDelegate.swift @@ -6,15 +6,14 @@ // Copyright © 2018年 Fin. All rights reserved. // -import UIKit -import Material -import UserNotifications -import RealmSwift import IceCream +import Material +import RealmSwift +import UIKit +import UserNotifications @UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate{ - +class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { var window: UIWindow? var syncEngine: SyncEngine? func setupRealm() { @@ -23,18 +22,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD let config = Realm.Configuration( fileURL: fileUrl, schemaVersion: 13, - migrationBlock: { migration, oldSchemaVersion in + migrationBlock: { _, oldSchemaVersion in // We haven’t migrated anything yet, so oldSchemaVersion == 0 - if (oldSchemaVersion < 1) { + if oldSchemaVersion < 1 { // Nothing to do! // Realm will automatically detect new properties and removed properties // And will update the schema on disk automatically } - }) + } + ) // Tell Realm to use this new configuration object for the default Realm Realm.Configuration.defaultConfiguration = config - //iCloud 同步 + // iCloud 同步 syncEngine = SyncEngine(objects: [ SyncObject(type: Message.self) ], databaseScope: .private) @@ -44,54 +44,54 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD print("message count: \(realm?.objects(Message.self).count ?? 0)") #endif } - + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - //必须在应用一开始就配置,否则应用可能提前在配置之前试用了 Realm() ,则会创建两个独立数据库。 + // 必须在应用一开始就配置,否则应用可能提前在配置之前试用了 Realm() ,则会创建两个独立数据库。 setupRealm() - + self.window = UIWindow(frame: UIScreen.main.bounds) if #available(iOS 13.0, *) { self.window?.overrideUserInterfaceStyle = .light } let tabBarController = StateStorageTabBarController() tabBarController.tabBar.tintColor = UIColor.black - + self.window?.backgroundColor = UIColor.black self.window?.rootViewController = BarkSnackbarController( rootViewController: tabBarController ) - + tabBarController.viewControllers = [ - BarkNavigationController(rootViewController:HomeViewController(viewModel: HomeViewModel())), - BarkNavigationController(rootViewController:MessageListViewController(viewModel: MessageListViewModel())), - BarkNavigationController(rootViewController:MessageSettingsViewController(viewModel: MessageSettingsViewModel())), + BarkNavigationController(rootViewController: HomeViewController(viewModel: HomeViewModel())), + BarkNavigationController(rootViewController: MessageListViewController(viewModel: MessageListViewModel())), + BarkNavigationController(rootViewController: MessageSettingsViewController(viewModel: MessageSettingsViewModel())) ] - + let tabBarItems = [UITabBarItem(title: NSLocalizedString("service"), image: UIImage(named: "baseline_gite_black_24pt"), tag: 0), UITabBarItem(title: NSLocalizedString("historyMessage"), image: Icon.history, tag: 1), UITabBarItem(title: NSLocalizedString("settings"), image: UIImage(named: "baseline_manage_accounts_black_24pt"), tag: 2)] - for (index , viewController) in tabBarController.viewControllers!.enumerated() { + for (index, viewController) in tabBarController.viewControllers!.enumerated() { viewController.tabBarItem = tabBarItems[index] } - + self.window?.makeKeyAndVisible() UNUserNotificationCenter.current().delegate = self UNUserNotificationCenter.current().setNotificationCategories([ UNNotificationCategory(identifier: "myNotificationCategory", actions: [ UNNotificationAction(identifier: "copy", title: NSLocalizedString("Copy2"), options: UNNotificationActionOptions.foreground) - ], intentIdentifiers: [], options: .customDismissAction) - ]) + ], intentIdentifiers: [], options: .customDismissAction) + ]) - UNUserNotificationCenter.current().getNotificationSettings { (settings) in + UNUserNotificationCenter.current().getNotificationSettings { settings in dispatch_sync_safely_main_queue { if settings.authorizationStatus == .authorized { Client.shared.registerForRemoteNotifications() } } } - - //调整返回按钮样式 + + // 调整返回按钮样式 let bar = UINavigationBar.appearance(whenContainedInInstancesOf: [BarkNavigationController.self]) bar.backIndicatorImage = UIImage(named: "back") bar.backIndicatorTransitionMaskImage = UIImage(named: "back") @@ -103,56 +103,57 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { print(error) } - + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let deviceTokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() Settings[.deviceToken] = deviceTokenString - - //注册设备 + + // 注册设备 Client.shared.bindDeviceToken() } + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { notificatonHandler(userInfo: notification.request.content.userInfo) } + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { notificatonHandler(userInfo: response.notification.request.content.userInfo) } - private func notificatonHandler(userInfo:[AnyHashable:Any]){ - + + private func notificatonHandler(userInfo: [AnyHashable: Any]) { let navigationController = Client.shared.currentNavigationController - func presentController(){ - let alert = (userInfo["aps"] as? [String:Any])?["alert"] as? [String:Any] + func presentController() { + let alert = (userInfo["aps"] as? [String: Any])?["alert"] as? [String: Any] let title = alert?["title"] as? String let body = alert?["body"] as? String - let url:URL? = { + let url: URL? = { if let url = userInfo["url"] as? String { return URL(string: url) } return nil }() - - //URL 直接打开 + + // URL 直接打开 if let url = url { - if ["http","https"].contains(url.scheme?.lowercased() ?? ""){ + if ["http", "https"].contains(url.scheme?.lowercased() ?? "") { navigationController?.present(BarkSFSafariViewController(url: url), animated: true, completion: nil) } - else{ + else { UIApplication.shared.open(url, options: [:], completionHandler: nil) } return } - - + let alertController = UIAlertController(title: title, message: body, preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: "复制内容", style: .default, handler: { (_) in + alertController.addAction(UIAlertAction(title: "复制内容", style: .default, handler: { _ in if let copy = userInfo["copy"] as? String { UIPasteboard.general.string = copy } - else{ + else { UIPasteboard.general.string = body } })) - alertController.addAction(UIAlertAction(title: "更多操作", style: .default, handler: { (_) in + alertController.addAction(UIAlertAction(title: "更多操作", style: .default, handler: { _ in var shareContent = "" if let title = title { shareContent += "\(title)\n" @@ -160,15 +161,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD if let body = body { shareContent += "\(body)\n" } - for (key,value) in userInfo { - if ["aps","title","body","url"].contains((key as? String) ?? "") { + for (key, value) in userInfo { + if ["aps", "title", "body", "url"].contains((key as? String) ?? "") { continue } shareContent += "\(key): \(value) \n" } - var items:[Any] = [] + var items: [Any] = [] items.append(shareContent) - if let url = url{ + if let url = url { items.append(url) } let controller = UIApplication.shared.keyWindow?.rootViewController @@ -177,16 +178,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD controller?.present(activityController, animated: true, completion: nil) })) alertController.addAction(UIAlertAction(title: "取消", style: .cancel, handler: nil)) - + navigationController?.present(alertController, animated: true, completion: nil) } - + if let presentedController = navigationController?.presentedViewController { presentedController.dismiss(animated: false) { presentController() } } - else{ + else { presentController() } } @@ -202,7 +203,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } func applicationWillEnterForeground(_ application: UIApplication) { - if (Client.shared.key?.count ?? 0) <= 0{ + if (Client.shared.key?.count ?? 0) <= 0 { Client.shared.bindDeviceToken() } } @@ -214,7 +215,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - - } - diff --git a/BarkTests/BarkTests.swift b/BarkTests/BarkTests.swift index 1678b72..b37fb29 100644 --- a/BarkTests/BarkTests.swift +++ b/BarkTests/BarkTests.swift @@ -9,7 +9,6 @@ import XCTest class BarkTests: XCTestCase { - override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. } @@ -29,5 +28,4 @@ class BarkTests: XCTestCase { // Put the code you want to measure the time of here. } } - } diff --git a/Common/ArchiveSettingManager.swift b/Common/ArchiveSettingManager.swift index e790d06..32f9468 100644 --- a/Common/ArchiveSettingManager.swift +++ b/Common/ArchiveSettingManager.swift @@ -10,17 +10,17 @@ import UIKit class ArchiveSettingManager: NSObject { static let shared = ArchiveSettingManager() - let defaults = UserDefaults.init(suiteName: "group.bark") + let defaults = UserDefaults(suiteName: "group.bark") var isArchive: Bool { get { - return defaults?.value(forKey: "isArchive") as? Bool ?? true - + return defaults?.value(forKey: "isArchive") as? Bool ?? true } - set{ + set { defaults?.set(newValue, forKey: "isArchive") } } - private override init(){ + + override private init() { super.init() } } diff --git a/Common/BarkSettings.swift b/Common/BarkSettings.swift index 6d87766..f858f42 100644 --- a/Common/BarkSettings.swift +++ b/Common/BarkSettings.swift @@ -6,10 +6,10 @@ // Copyright © 2018 Fin. All rights reserved. // -import UIKit import DefaultsKit +import UIKit -enum BarkSettingKey:String { +enum BarkSettingKey: String { /// 存放key case key = "me.fin.bark.key" case servers = "me.fin.bark.servers" @@ -20,13 +20,11 @@ enum BarkSettingKey:String { case selectedViewControllerIndex = "me.fin.bark.selectedViewControllerIndex" } -class BarkSettings{ +class BarkSettings { static let shared = BarkSettings() - private init(){ - - } + private init() {} - subscript(key:String) -> String? { + subscript(key: String) -> String? { get { let storeKey = Key(key) return Defaults.shared.get(for: storeKey) @@ -42,7 +40,7 @@ class BarkSettings{ } } - subscript(key:BarkSettingKey) -> String? { + subscript(key: BarkSettingKey) -> String? { get { return self[key.rawValue] } @@ -51,7 +49,7 @@ class BarkSettings{ } } - subscript(key:String) -> T? { + subscript(key: String) -> T? { get { let storeKey = Key(key) return Defaults.shared.get(for: storeKey) @@ -66,7 +64,8 @@ class BarkSettings{ } } } - subscript(key:BarkSettingKey) -> T? { + + subscript(key: BarkSettingKey) -> T? { get { return self[key.rawValue] } diff --git a/Common/Client.swift b/Common/Client.swift index 6b99e28..19c6e9c 100644 --- a/Common/Client.swift +++ b/Common/Client.swift @@ -6,31 +6,29 @@ // Copyright © 2018 Fin. All rights reserved. // +import RxCocoa +import RxSwift import UIKit import UserNotifications -import RxSwift -import RxCocoa class Client: NSObject { static let shared = Client() - private override init() { + override private init() { super.init() } - var currentNavigationController:UINavigationController? { - get { - let controller = UIApplication.shared.delegate?.window??.rootViewController as? BarkSnackbarController - let nav = (controller?.rootViewController as? UITabBarController)?.selectedViewController as? UINavigationController - return nav - } + + var currentNavigationController: UINavigationController? { + let controller = UIApplication.shared.delegate?.window??.rootViewController as? BarkSnackbarController + let nav = (controller?.rootViewController as? UITabBarController)?.selectedViewController as? UINavigationController + return nav } - var currentTabBarController:StateStorageTabBarController? { - get { - let controller = UIApplication.shared.delegate?.window??.rootViewController as? BarkSnackbarController - return controller?.rootViewController as? StateStorageTabBarController - } + + var currentTabBarController: StateStorageTabBarController? { + let controller = UIApplication.shared.delegate?.window??.rootViewController as? BarkSnackbarController + return controller?.rootViewController as? StateStorageTabBarController } - let appVersion:String = { + let appVersion: String = { var version = "0.0.0" if let infoDict = Bundle.main.infoDictionary { if let appVersion = infoDict["CFBundleVersion"] as? String { @@ -40,10 +38,10 @@ class Client: NSObject { return version }() - private var _key:String? - var key:String? { + private var _key: String? + var key: String? { get { - if _key == nil, let aKey = Settings[.key]{ + if _key == nil, let aKey = Settings[.key] { _key = aKey } return _key @@ -62,24 +60,24 @@ class Client: NSObject { var state = BehaviorRelay(value: .ok) - var dispose:Disposable? - func bindDeviceToken(){ - if let token = Settings[.deviceToken] , token.count > 0{ + var dispose: Disposable? + func bindDeviceToken() { + if let token = Settings[.deviceToken], token.count > 0 { dispose?.dispose() dispose = BarkApi.provider .request(.register( - key: key, - devicetoken: token)) + key: key, + devicetoken: token)) .filterResponseError() - .map { (json) -> ClienState in + .map { json -> ClienState in switch json { case .success(let json): - if let key = json["data","key"].rawString() { + if let key = json["data", "key"].rawString() { Client.shared.key = key return .ok } - else{ + else { return .serverError } case .failure: @@ -92,13 +90,13 @@ class Client: NSObject { func registerForRemoteNotifications() { let center = UNUserNotificationCenter.current() - center.requestAuthorization(options: [.alert , .sound , .badge], completionHandler: {(_ granted: Bool, _ error: Error?) -> Void in + center.requestAuthorization(options: [.alert, .sound, .badge], completionHandler: { (_ granted: Bool, _: Error?) -> Void in if granted { dispatch_sync_safely_main_queue { UIApplication.shared.registerForRemoteNotifications() } } - else{ + else { print("没有打开推送") } }) diff --git a/Common/Date+Extension.swift b/Common/Date+Extension.swift index 018a517..7fb2066 100644 --- a/Common/Date+Extension.swift +++ b/Common/Date+Extension.swift @@ -9,23 +9,22 @@ import UIKit extension Date { - func formatString(format:String) -> String { + func formatString(format: String) -> String { let formatter = DateFormatter() formatter.dateFormat = format return formatter.string(for: self) ?? "" } - + func agoFormatString() -> String { - let clendar = NSCalendar(calendarIdentifier: .gregorian) - let cps = clendar?.components([ .hour, .minute, .second, .day, .month, .year], from: self, to: Date(), options: .wrapComponents) - + let cps = clendar?.components([.hour, .minute, .second, .day, .month, .year], from: self, to: Date(), options: .wrapComponents) + let year = cps!.year! let month = cps!.month! let day = cps!.day! let hour = cps!.hour! let minute = cps!.minute! - + if year > 0 || month > 0 || day > 0 || hour > 12 { return formatString(format: "yyyy-MM-dd HH:mm") } @@ -44,20 +43,24 @@ extension Date { extension Date { static var yesterday: Date { return Date().dayBefore } - static var tomorrow: Date { return Date().dayAfter } - static var lastHour: Date { return Calendar.current.date(byAdding: .hour, value: -1, to: Date())! } + static var tomorrow: Date { return Date().dayAfter } + static var lastHour: Date { return Calendar.current.date(byAdding: .hour, value: -1, to: Date())! } var dayBefore: Date { return Calendar.current.date(byAdding: .day, value: -1, to: noon)! } + var dayAfter: Date { return Calendar.current.date(byAdding: .day, value: 1, to: noon)! } + var noon: Date { return Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: self)! } + var month: Int { - return Calendar.current.component(.month, from: self) + return Calendar.current.component(.month, from: self) } + var isLastDayOfMonth: Bool { return dayAfter.month != month } diff --git a/Common/Defines.swift b/Common/Defines.swift index 657eaf4..ccbb8fb 100644 --- a/Common/Defines.swift +++ b/Common/Defines.swift @@ -1,4 +1,4 @@ - // +// // Defines.swift // Bark // @@ -9,7 +9,7 @@ import UIKit /// 将代码安全的运行在主线程 -func dispatch_sync_safely_main_queue(_ block: ()->()) { +func dispatch_sync_safely_main_queue(_ block: () -> ()) { if Thread.isMainThread { block() } else { @@ -20,29 +20,28 @@ func dispatch_sync_safely_main_queue(_ block: ()->()) { } extension UIViewController { - func showSnackbar(text:String) { + func showSnackbar(text: String) { self.snackbarController?.snackbar.text = text self.snackbarController?.animate(snackbar: .visible) self.snackbarController?.animate(snackbar: .hidden, delay: 3) } } -func NSLocalizedString( _ key:String ) -> String { +func NSLocalizedString(_ key: String) -> String { return NSLocalizedString(key, comment: "") } let kNavigationHeight: CGFloat = { - return kSafeAreaInsets.top + 44 + kSafeAreaInsets.top + 44 }() -let kSafeAreaInsets:UIEdgeInsets = { - if #available(iOS 12.0, *){ +let kSafeAreaInsets: UIEdgeInsets = { + if #available(iOS 12.0, *) { return UIWindow().safeAreaInsets - } - else if #available(iOS 11.0, *){ + } else if #available(iOS 11.0, *) { let inset = UIWindow().safeAreaInsets - if inset.top > 0 { return inset} - //iOS 11下,不是全面屏的手机 safeAreaInsets.top 是 0,与iOS12 不一致,这里强行让他们保持一致,方便开发 + if inset.top > 0 { return inset } + // iOS 11下,不是全面屏的手机 safeAreaInsets.top 是 0,与iOS12 不一致,这里强行让他们保持一致,方便开发 } return UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0) }() diff --git a/Common/MJRefresh+Rx.swift b/Common/MJRefresh+Rx.swift index 2a9bf87..fafb435 100644 --- a/Common/MJRefresh+Rx.swift +++ b/Common/MJRefresh+Rx.swift @@ -7,14 +7,13 @@ // import Foundation +import MJRefresh import RxCocoa import RxSwift -import MJRefresh -extension Reactive where Base : MJRefreshComponent { +extension Reactive where Base: MJRefreshComponent { var refresh: ControlEvent { - - let source = Observable.create {[weak control = self.base] (observer) -> Disposable in + let source = Observable.create { [weak control = self.base] observer -> Disposable in MainScheduler.ensureExecutingOnScheduler() guard let control = control else { observer.onCompleted() @@ -27,10 +26,8 @@ extension Reactive where Base : MJRefreshComponent { } return ControlEvent(events: source) } - } - enum MJRefreshAction { /// 不做任何事情 case none @@ -48,16 +45,14 @@ enum MJRefreshAction { case resetNomoreData } -extension Reactive where Base:UIScrollView { - +extension Reactive where Base: UIScrollView { /// 执行的操作类型 - var refreshAction:Binder { - - return Binder(base) { (target, action) in - - switch action{ + var refreshAction: Binder { + return Binder(base) { target, action in + + switch action { case .begainRefresh: - //下拉刷新使用 UIRefreshControl + // 下拉刷新使用 UIRefreshControl if let control = target.refreshControl { control.beginRefreshing() } @@ -66,26 +61,24 @@ extension Reactive where Base:UIScrollView { control.endRefreshing() } case .begainLoadmore: - if let footer = target.mj_footer { + if let footer = target.mj_footer { footer.beginRefreshing() } case .endLoadmore: - if let footer = target.mj_footer { + if let footer = target.mj_footer { footer.endRefreshing() } case .showNomoreData: - if let footer = target.mj_footer { + if let footer = target.mj_footer { footer.endRefreshingWithNoMoreData() } case .resetNomoreData: - if let footer = target.mj_footer { + if let footer = target.mj_footer { footer.resetNoMoreData() } - break case .none: break } } } - } diff --git a/Common/Moya/BarkApi.swift b/Common/Moya/BarkApi.swift index 90f00b2..25bdb28 100644 --- a/Common/Moya/BarkApi.swift +++ b/Common/Moya/BarkApi.swift @@ -9,21 +9,22 @@ import UIKit enum BarkApi { - case ping(baseURL:String?) - case register(key:String? , devicetoken:String) //注册设备 + case ping(baseURL: String?) + case register(key: String?, devicetoken: String) // 注册设备 } extension BarkApi: BarkTargetType { var baseURL: URL { - if case let .ping(urlStr) = self, let url = URL(string: urlStr ?? "") { + if case let .ping(urlStr) = self, let url = URL(string: urlStr ?? "") { return url } return URL(string: ServerManager.shared.currentAddress)! } - var parameters: [String : Any]? { + + var parameters: [String: Any]? { switch self { case let .register(key, devicetoken): - var params = ["devicetoken":devicetoken] + var params = ["devicetoken": devicetoken] if let key = key { params["key"] = key } @@ -32,7 +33,7 @@ extension BarkApi: BarkTargetType { return nil } } - + var path: String { switch self { case .ping: @@ -41,6 +42,4 @@ extension BarkApi: BarkTargetType { return "/register" } } - - } diff --git a/Common/Moya/BarkTargetType.swift b/Common/Moya/BarkTargetType.swift index fbb7634..ff9ec43 100644 --- a/Common/Moya/BarkTargetType.swift +++ b/Common/Moya/BarkTargetType.swift @@ -6,21 +6,22 @@ // Copyright © 2018 Fin. All rights reserved. // -import UIKit import Moya import RxSwift +import UIKit -//保存全局Providers -fileprivate var retainProviders:[String: Any] = [:] +// 保存全局Providers +private var retainProviders: [String: Any] = [:] protocol BarkTargetType: TargetType { var parameters: [String: Any]? { get } } extension BarkTargetType { - var headers: [String : String]? { + var headers: [String: String]? { return nil } + var baseURL: URL { return URL(string: ServerManager.shared.currentAddress)! } @@ -42,21 +43,19 @@ extension BarkTargetType { } var requestTaskWithParameters: Task { - get { - //默认参数 - var defaultParameters:[String:Any] = [:] - //协议参数 - if let parameters = self.parameters { - for (key, value) in parameters { - defaultParameters[key] = value - } + // 默认参数 + var defaultParameters: [String: Any] = [:] + // 协议参数 + if let parameters = self.parameters { + for (key, value) in parameters { + defaultParameters[key] = value } - return Task.requestParameters(parameters: defaultParameters, encoding: parameterEncoding) } + return Task.requestParameters(parameters: defaultParameters, encoding: parameterEncoding) } static var networkActivityPlugin: PluginType { - return NetworkActivityPlugin { (change, type) in + return NetworkActivityPlugin { change, _ in switch change { case .began: dispatch_sync_safely_main_queue { @@ -71,9 +70,9 @@ extension BarkTargetType { } /// 实现此协议的类,将自动获得用该类实例化的 provider 对象 - static var provider: RxSwift.Reactive< MoyaProvider > { + static var provider: RxSwift.Reactive> { let key = "\(Self.self)" - if let provider = retainProviders[key] as? RxSwift.Reactive< MoyaProvider > { + if let provider = retainProviders[key] as? RxSwift.Reactive> { return provider } let provider = Self.weakProvider @@ -82,18 +81,18 @@ extension BarkTargetType { } /// 不被全局持有的 Provider ,使用时,需要持有它,否则将立即释放,请求随即终止 - static var weakProvider: RxSwift.Reactive< MoyaProvider > { - var plugins:[PluginType] = [networkActivityPlugin] + static var weakProvider: RxSwift.Reactive> { + var plugins: [PluginType] = [networkActivityPlugin] #if DEBUG plugins.append(LogPlugin()) #endif - let provider = MoyaProvider(plugins:plugins) + let provider = MoyaProvider(plugins: plugins) return provider.rx } } -extension RxSwift.Reactive where Base: MoyaProviderType { - public func request(_ token: Base.Target, callbackQueue: DispatchQueue? = nil) -> Observable { +public extension RxSwift.Reactive where Base: MoyaProviderType { + func request(_ token: Base.Target, callbackQueue: DispatchQueue? = nil) -> Observable { return Single.create { [weak base] single in let cancellableToken = base?.request(token, callbackQueue: callbackQueue, progress: nil) { result in switch result { @@ -107,11 +106,11 @@ extension RxSwift.Reactive where Base: MoyaProviderType { return Disposables.create { cancellableToken?.cancel() } - }.asObservable() + }.asObservable() } } -fileprivate class LogPlugin: PluginType{ +private class LogPlugin: PluginType { func willSend(_ request: RequestType, target: TargetType) { print("\n-------------------\n准备请求: \(target.path)") print("请求方式: \(target.method.rawValue)") @@ -119,8 +118,8 @@ fileprivate class LogPlugin: PluginType{ print(params) } print("\n") - } + func didReceive(_ result: Result, target: TargetType) { print("\n-------------------\n请求结束: \(target.path)") if let data = try? result.get().data, let resutl = String(data: data, encoding: String.Encoding.utf8) { diff --git a/Common/Moya/Observable+Extension.swift b/Common/Moya/Observable+Extension.swift index aadb1bd..d8462d2 100644 --- a/Common/Moya/Observable+Extension.swift +++ b/Common/Moya/Observable+Extension.swift @@ -8,13 +8,13 @@ import UIKit -import UIKit -import RxSwift -import ObjectMapper -import SwiftyJSON import Moya +import ObjectMapper +import RxSwift +import SwiftyJSON +import UIKit -public enum ApiError : Swift.Error { +public enum ApiError: Swift.Error { case Error(info: String) case AccountBanned(info: String) } @@ -25,9 +25,9 @@ extension Swift.Error { return self.localizedDescription } switch err { - case let .Error(info): + case .Error(let info): return info - case let .AccountBanned(info): + case .AccountBanned(let info): return info } } @@ -35,23 +35,23 @@ extension Swift.Error { extension Observable where Element: Moya.Response { /// 过滤 HTTP 错误,例如超时,请求失败等 - func filterHttpError() -> Observable> { + func filterHttpError() -> Observable> { return catchErrorJustReturn(Element(statusCode: 599, data: Data())) - .map { (response) -> Result in - if (200...209) ~= response.statusCode { - return .success(response) + .map { response -> Result in + if (200 ... 209) ~= response.statusCode { + return .success(response) + } + else { + return .failure(ApiError.Error(info: "网络错误")) + } } - else{ - return .failure(ApiError.Error(info: "网络错误")) - } - } } /// 过滤逻辑错误,例如协议里返回 错误CODE - func filterResponseError() -> Observable> { + func filterResponseError() -> Observable> { return filterHttpError() - .map{ response -> Result in + .map { response -> Result in switch response { case .success(let element): do { @@ -59,11 +59,12 @@ extension Observable where Element: Moya.Response { if let codeStr = json["code"].rawString(), let code = Int(codeStr), - code == 200{ + code == 200 + { return .success(json) } - else{ - var msg:String = "" + else { + var msg: String = "" if json["message"].exists() { msg = json["message"].rawString()! } @@ -73,7 +74,7 @@ extension Observable where Element: Moya.Response { catch { return .failure(ApiError.Error(info: error.rawString())) } - case .failure(let error) : + case .failure(let error): return .failure(ApiError.Error(info: error.rawString())) } } @@ -84,18 +85,18 @@ extension Observable where Element: Moya.Response { /// - Parameters: /// - typeName: 要转换的Model Class /// - dataPath: 从哪个节点开始转换,例如 ["data","links"] - func mapResponseToObj(_ typeName: T.Type , dataPath:[String] = ["data"] ) -> Observable> { - return filterResponseError().map{ json in + func mapResponseToObj(_ typeName: T.Type, dataPath: [String] = ["data"]) -> Observable> { + return filterResponseError().map { json in switch json { case .success(let json): var rootJson = json - if dataPath.count > 0{ + if dataPath.count > 0 { rootJson = rootJson[dataPath] } - if let model: T = self.resultFromJSON(json: rootJson) { + if let model: T = self.resultFromJSON(json: rootJson) { return .success(model) } - else{ + else { return .failure(ApiError.Error(info: "json 转换失败")) } case .failure(let error): @@ -105,24 +106,24 @@ extension Observable where Element: Moya.Response { } /// 将Response 转换成 JSON Model Array - func mapResponseToObjArray(_ type: T.Type, dataPath:[String] = ["data"] ) -> Observable> { - return filterResponseError().map{ json in + func mapResponseToObjArray(_ type: T.Type, dataPath: [String] = ["data"]) -> Observable> { + return filterResponseError().map { json in switch json { case .success(let json): - var rootJson = json; - if dataPath.count > 0{ + var rootJson = json + if dataPath.count > 0 { rootJson = rootJson[dataPath] } var result = [T]() - guard let jsonArray = rootJson.array else{ + guard let jsonArray = rootJson.array else { return .failure(ApiError.Error(info: "Root Json 不是 Array")) } - for json in jsonArray{ + for json in jsonArray { if let jsonModel: T = self.resultFromJSON(json: json) { result.append(jsonModel) } - else{ + else { return .failure(ApiError.Error(info: "json 转换失败")) } } @@ -131,15 +132,15 @@ extension Observable where Element: Moya.Response { case .failure(let error): return .failure(error) } - } } - private func resultFromJSON(jsonString:String) -> T? { + private func resultFromJSON(jsonString: String) -> T? { return T(JSONString: jsonString) } - private func resultFromJSON(json:JSON) -> T? { - if let str = json.rawString(){ + + private func resultFromJSON(json: JSON) -> T? { + if let str = json.rawString() { return resultFromJSON(jsonString: str) } return nil diff --git a/Common/Operators.swift b/Common/Operators.swift index 19e9a08..0af0e0c 100644 --- a/Common/Operators.swift +++ b/Common/Operators.swift @@ -5,8 +5,8 @@ // Created by Krunoslav Zaher on 12/6/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // -import RxSwift import RxCocoa +import RxSwift #if os(iOS) import UIKit #elseif os(macOS) @@ -14,7 +14,7 @@ import AppKit #endif // Two way binding operator between control property and relay, that's all it takes. -infix operator <-> : DefaultPrecedence +infix operator <->: DefaultPrecedence #if os(iOS) func nonMarkedText(_ textInput: UITextInput) -> String? { @@ -22,8 +22,9 @@ func nonMarkedText(_ textInput: UITextInput) -> String? { let end = textInput.endOfDocument guard let rangeAll = textInput.textRange(from: start, to: end), - let text = textInput.text(in: rangeAll) else { - return nil + let text = textInput.text(in: rangeAll) + else { + return nil } guard let markedTextRange = textInput.markedTextRange else { @@ -31,7 +32,8 @@ func nonMarkedText(_ textInput: UITextInput) -> String? { } guard let startRange = textInput.textRange(from: start, to: markedTextRange.start), - let endRange = textInput.textRange(from: markedTextRange.end, to: end) else { + let endRange = textInput.textRange(from: markedTextRange.end, to: end) + else { return text } @@ -42,7 +44,7 @@ func <-> (textInput: TextInput, relay: BehaviorRelay) -> Dis let bindToUIDisposable = relay.bind(to: textInput.text) let bindToRelay = textInput.text - .subscribe(onNext: { [weak base = textInput.base] n in + .subscribe(onNext: { [weak base = textInput.base] _ in guard let base = base else { return } @@ -53,7 +55,7 @@ func <-> (textInput: TextInput, relay: BehaviorRelay) -> Dis In some cases `textInput.textRangeFromPosition(start, toPosition: end)` will return nil even though the underlying value is not nil. This appears to be an Apple bug. If it's not, and we are doing something wrong, please let us know. The can be reproed easily if replace bottom code with - + if nonMarkedTextValue != relay.value { relay.accept(nonMarkedTextValue ?? "") } @@ -62,7 +64,7 @@ func <-> (textInput: TextInput, relay: BehaviorRelay) -> Dis if let nonMarkedTextValue = nonMarkedTextValue, nonMarkedTextValue != relay.value { relay.accept(nonMarkedTextValue) } - }, onCompleted: { + }, onCompleted: { bindToUIDisposable.dispose() }) @@ -77,7 +79,7 @@ func <-> (property: ControlProperty, relay: BehaviorRelay) -> Disposabl "That will usually work ok, but for some languages that use IME, that simplistic method could cause unexpected issues because it will return intermediate results while text is being inputed.\n" + "REMEDY: Just use `textField <-> relay` instead of `textField.rx.text <-> relay`.\n" + "Find out more here: https://github.com/ReactiveX/RxSwift/issues/649\n" - ) + ) #endif } @@ -85,7 +87,7 @@ func <-> (property: ControlProperty, relay: BehaviorRelay) -> Disposabl let bindToRelay = property .subscribe(onNext: { n in relay.accept(n) - }, onCompleted: { + }, onCompleted: { bindToUIDisposable.dispose() }) diff --git a/Common/Reusable.swift b/Common/Reusable.swift index 6a2519f..2079c54 100644 --- a/Common/Reusable.swift +++ b/Common/Reusable.swift @@ -6,13 +6,13 @@ // Copyright © 2020 Fin. All rights reserved. // -import UIKit -import RxSwift import RxCocoa +import RxSwift +import UIKit private var prepareForReuseBag: Int8 = 0 -@objc public protocol Reusable : class { +@objc public protocol Reusable: class { func prepareForReuse() } @@ -24,17 +24,17 @@ extension Reactive where Base: Reusable { var prepareForReuse: Observable { return Observable.of(sentMessage(#selector(Base.prepareForReuse)).map { _ in }, deallocated).merge() } - + var reuseBag: DisposeBag { MainScheduler.ensureExecutingOnScheduler() - + if let bag = objc_getAssociatedObject(base, &prepareForReuseBag) as? DisposeBag { return bag } - + let bag = DisposeBag() objc_setAssociatedObject(base, &prepareForReuseBag, bag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) - + _ = sentMessage(#selector(Base.prepareForReuse)) .subscribe(onNext: { [weak base] _ in guard let strongBase = base else { @@ -43,7 +43,7 @@ extension Reactive where Base: Reusable { let newBag = DisposeBag() objc_setAssociatedObject(strongBase, &prepareForReuseBag, newBag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) }) - + return bag } } diff --git a/Common/ServerManager.swift b/Common/ServerManager.swift index 063f528..5024149 100644 --- a/Common/ServerManager.swift +++ b/Common/ServerManager.swift @@ -12,39 +12,41 @@ let defaultServer = "https://api.day.app" class ServerManager: NSObject { static let shared = ServerManager() - private override init() { - if let servers:Set = Settings[.servers] { + override private init() { + if let servers: Set = Settings[.servers] { self.servers = servers } - + if let address = Settings[.currentServer] { self.currentAddress = address } - else{ + else { self.currentAddress = self.servers.first ?? defaultServer } super.init() } - - var servers:Set = [defaultServer] - var currentAddress:String { - didSet{ + + var servers: Set = [defaultServer] + var currentAddress: String { + didSet { Settings[.currentServer] = currentAddress } } - - func addServer(server:String){ + + func addServer(server: String) { self.servers.insert(server) Settings[.servers] = self.servers } - func removeServer(server:String){ + + func removeServer(server: String) { self.servers.remove(server) if self.servers.count <= 0 { self.servers.insert(defaultServer) } Settings[.servers] = self.servers } - + + @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/Common/String+Extension.swift b/Common/String+Extension.swift index 906a5d6..bf96566 100644 --- a/Common/String+Extension.swift +++ b/Common/String+Extension.swift @@ -9,14 +9,14 @@ import UIKit extension String { - //将原始的url编码为合法的url + // 将原始的url编码为合法的url func urlEncoded() -> String { let encodeUrlString = self.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) return encodeUrlString ?? "" } - - //将编码后的url转换回原始的url + + // 将编码后的url转换回原始的url func urlDecoded() -> String { return self.removingPercentEncoding ?? "" } diff --git a/Common/UIColor+Extension.swift b/Common/UIColor+Extension.swift index ac09758..f41e9ce 100644 --- a/Common/UIColor+Extension.swift +++ b/Common/UIColor+Extension.swift @@ -9,20 +9,21 @@ import UIKit extension UIColor { - convenience public init(r255:CGFloat, g255:CGFloat, b255:CGFloat, a255:CGFloat = 255) { + public convenience init(r255: CGFloat, g255: CGFloat, b255: CGFloat, a255: CGFloat = 255) { self.init(red: r255/255, green: g255/255, blue: b255/255, alpha: a255/255) } - class func image(color:UIColor, size:CGSize = CGSize(width: 1, height: 1)) -> UIImage{ + + class func image(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage { UIGraphicsBeginImageContext(size) let context = UIGraphicsGetCurrentContext() context?.setFillColor(color.cgColor) context?.fill(CGRect(origin: CGPoint.zero, size: size)) - + let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - return image! //context应该不会没get到吧~ 所以直接强解了 + return image! // context应该不会没get到吧~ 所以直接强解了 } - + var image: UIImage { return UIColor.image(color: self) } diff --git a/Common/ViewModelType.swift b/Common/ViewModelType.swift index ddd3fb6..e9c2e5d 100644 --- a/Common/ViewModelType.swift +++ b/Common/ViewModelType.swift @@ -16,4 +16,4 @@ protocol ViewModelType { func transform(input: Input) -> Output } -class ViewModel:NSObject{ } +class ViewModel: NSObject {} diff --git a/Controller/BarkNavigationController.swift b/Controller/BarkNavigationController.swift index 3e865de..82e4b71 100644 --- a/Controller/BarkNavigationController.swift +++ b/Controller/BarkNavigationController.swift @@ -6,11 +6,11 @@ // Copyright © 2018 Fin. All rights reserved. // -import UIKit import Material import RxSwift +import UIKit -class BarkNavigationController: UINavigationController{ +class BarkNavigationController: UINavigationController { override func viewDidLoad() { super.viewDidLoad() self.navigationBar.prefersLargeTitles = true @@ -18,7 +18,7 @@ class BarkNavigationController: UINavigationController{ } class BarkSnackbarController: SnackbarController { - override var childForStatusBarStyle: UIViewController?{ + override var childForStatusBarStyle: UIViewController? { return self.rootViewController } } @@ -31,13 +31,12 @@ enum TabPage: Int { } class StateStorageTabBarController: UITabBarController, UITabBarControllerDelegate { - // 标记当前显示的页面,再次点击相同的页面时当做页面点击事件。 var currentSelectedIndex: Int = 0 - + // 点击当前页面的 tabBarItem , 可以用以点击刷新当前页面等操作 lazy var tabBarItemDidClick: Observable = { - return self.rx.didSelect + self.rx.didSelect .flatMapLatest { _ -> Single in let single = Single.create { single in if self.currentSelectedIndex == self.selectedIndex { @@ -55,17 +54,16 @@ class StateStorageTabBarController: UITabBarController, UITabBarControllerDelega super.viewWillAppear(animated) if isFirstAppear { isFirstAppear = false - - //开启APP时,默认选择上次打开的页面 - if let index:Int = Settings[.selectedViewControllerIndex] { + + // 开启APP时,默认选择上次打开的页面 + if let index: Int = Settings[.selectedViewControllerIndex] { self.selectedIndex = index self.currentSelectedIndex = index } - //保存打开的页面Index - self.rx.didSelect.subscribe(onNext: {_ in + // 保存打开的页面Index + self.rx.didSelect.subscribe(onNext: { _ in Settings[.selectedViewControllerIndex] = self.selectedIndex }).disposed(by: rx.disposeBag) } - } } diff --git a/Controller/BarkSFSafariViewController.swift b/Controller/BarkSFSafariViewController.swift index 7098bb0..9202ae0 100644 --- a/Controller/BarkSFSafariViewController.swift +++ b/Controller/BarkSFSafariViewController.swift @@ -6,10 +6,9 @@ // Copyright © 2018 Fin. All rights reserved. // -import UIKit import SafariServices +import UIKit class BarkSFSafariViewController: SFSafariViewController { - override func viewDidLoad() { super.viewDidLoad() @@ -20,11 +19,8 @@ class BarkSFSafariViewController: SFSafariViewController { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } - - override var preferredStatusBarStyle: UIStatusBarStyle{ - get { - return .default - } - } + override var preferredStatusBarStyle: UIStatusBarStyle { + return .default + } } diff --git a/Controller/BaseViewController.swift b/Controller/BaseViewController.swift index 1cf2a05..5b1cfc6 100644 --- a/Controller/BaseViewController.swift +++ b/Controller/BaseViewController.swift @@ -6,26 +6,24 @@ // Copyright © 2018 Fin. All rights reserved. // -import UIKit import Material +import UIKit class BaseViewController: UIViewController { - - let viewModel:ViewModel - init(viewModel:ViewModel) { + let viewModel: ViewModel + init(viewModel: ViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) self.view.backgroundColor = Color.grey.lighten5 } + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override var preferredStatusBarStyle: UIStatusBarStyle{ - get { - return .lightContent - } + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent } override func viewDidLoad() { @@ -33,6 +31,7 @@ class BaseViewController: UIViewController { self.navigationItem.largeTitleDisplayMode = .automatic makeUI() } + var isViewModelBinded = false override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) @@ -42,10 +41,7 @@ class BaseViewController: UIViewController { } } - func makeUI() { - - } - func bindViewModel(){ - - } + func makeUI() {} + + func bindViewModel() {} } diff --git a/Controller/GroupFilterViewController.swift b/Controller/GroupFilterViewController.swift index a696faf..a7ec9d4 100644 --- a/Controller/GroupFilterViewController.swift +++ b/Controller/GroupFilterViewController.swift @@ -6,16 +6,15 @@ // Copyright © 2021 Fin. All rights reserved. // -import UIKit import Material +import MJRefresh import RealmSwift import RxCocoa import RxDataSources -import MJRefresh import RxSwift +import UIKit class GroupFilterViewController: BaseViewController { - let doneButton: BKButton = { let btn = BKButton() btn.setTitle(NSLocalizedString("done"), for: .normal) @@ -35,7 +34,7 @@ class GroupFilterViewController: BaseViewController { }() let tableView: UITableView = { - let tableView:UITableView = UITableView(frame: CGRect.zero, style: .insetGrouped) + let tableView = UITableView(frame: CGRect.zero, style: .insetGrouped) tableView.separatorStyle = .singleLine tableView.separatorColor = Color.grey.lighten3 tableView.backgroundColor = Color.grey.lighten5 @@ -49,9 +48,9 @@ class GroupFilterViewController: BaseViewController { self.view.addSubview(tableView) self.view.addSubview(showAllGroupsButton) - tableView.snp.makeConstraints { (make) in + tableView.snp.makeConstraints { make in make.top.equalToSuperview() - make.bottom.equalToSuperview().offset( (kSafeAreaInsets.bottom + 40) * -1) + make.bottom.equalToSuperview().offset((kSafeAreaInsets.bottom + 40) * -1) make.left.right.equalToSuperview() } showAllGroupsButton.snp.makeConstraints { make in @@ -62,6 +61,7 @@ class GroupFilterViewController: BaseViewController { self.tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 20)) } + override func bindViewModel() { guard let viewModel = self.viewModel as? GroupFilterViewModel else { return @@ -71,15 +71,15 @@ class GroupFilterViewController: BaseViewController { input: GroupFilterViewModel.Input( showAllGroups: self.showAllGroupsButton.rx .tap - .compactMap({[weak self] in - guard let strongSelf = self else {return nil} + .compactMap { [weak self] in + guard let strongSelf = self else { return nil } return !strongSelf.showAllGroupsButton.isSelected - }) + } .asDriver(onErrorDriveWith: .empty()), doneTap: self.doneButton.rx.tap.asDriver() )) - let dataSource = RxTableViewSectionedReloadDataSource> { (source, tableView, indexPath, item) -> UITableViewCell in + let dataSource = RxTableViewSectionedReloadDataSource> { _, tableView, _, item -> UITableViewCell in guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(GroupTableViewCell.self)") as? GroupTableViewCell else { return UITableViewCell() } @@ -95,13 +95,11 @@ class GroupFilterViewController: BaseViewController { .drive(self.showAllGroupsButton.rx.isSelected) .disposed(by: rx.disposeBag) - output.dismiss.drive(onNext: {[weak self] in + output.dismiss.drive(onNext: { [weak self] in self?.dismiss(animated: true, completion: nil) }) .disposed(by: rx.disposeBag) } } -extension GroupFilterViewController: UITableViewDelegate { - -} +extension GroupFilterViewController: UITableViewDelegate {} diff --git a/Controller/GroupFilterViewModel.swift b/Controller/GroupFilterViewModel.swift index 97e41a5..85b6fca 100644 --- a/Controller/GroupFilterViewModel.swift +++ b/Controller/GroupFilterViewModel.swift @@ -6,89 +6,86 @@ // Copyright © 2021 Fin. All rights reserved. // -import UIKit -import RxSwift -import RxDataSources -import RxCocoa import RealmSwift +import RxCocoa +import RxDataSources +import RxSwift +import UIKit struct GroupFilterModel { - var name:String? - var checked:Bool + var name: String? + var checked: Bool } -class GroupFilterViewModel: ViewModel,ViewModelType { - let groups:[GroupFilterModel] - init(groups:[GroupFilterModel]) { + +class GroupFilterViewModel: ViewModel, ViewModelType { + let groups: [GroupFilterModel] + init(groups: [GroupFilterModel]) { self.groups = groups } struct Input { - var showAllGroups:Driver - var doneTap:Driver + var showAllGroups: Driver + var doneTap: Driver } struct Output { - var groups:Driver<[ SectionModel]> - var isShowAllGroups:Driver - var dismiss:Driver + var groups: Driver<[SectionModel]> + var isShowAllGroups: Driver + var dismiss: Driver } var done = PublishRelay<[String?]>() func transform(input: Input) -> Output { - // 页面中的群组cellModel - let groupCellModels = self.groups.map({ filterModel in - return GroupCellViewModel(groupFilterModel: filterModel) - }) + let groupCellModels = self.groups.map { filterModel in + GroupCellViewModel(groupFilterModel: filterModel) + } - //点击显示所有群组或隐藏所有群组时,设置cell checked 勾选状态 + // 点击显示所有群组或隐藏所有群组时,设置cell checked 勾选状态 input.showAllGroups.drive(onNext: { isShowAllGroups in - groupCellModels.forEach { model in + groupCellModels.forEach { model in model.checked.accept(isShowAllGroups) } }).disposed(by: rx.disposeBag) - //cell checked 状态改变 + // cell checked 状态改变 let checkChanged = Observable.merge(groupCellModels.map { model in - return model.checked.asObservable() + model.checked.asObservable() }) // 是否勾选了所有群组 let isShowAllGroups = checkChanged - .map{_ in - return groupCellModels.filter { viewModel in - return viewModel.checked.value - }.count >= groupCellModels.count - } + .map { _ in + groupCellModels.filter { viewModel in + viewModel.checked.value + }.count >= groupCellModels.count + } input.doneTap.map { () -> [String?] in let isShowAllGroups = groupCellModels.filter { viewModel in - return viewModel.checked.value + viewModel.checked.value }.count >= groupCellModels.count if isShowAllGroups { return [] } return groupCellModels - .filter { $0.checked.value} + .filter { $0.checked.value } .map { $0.name.value } } .asObservable() .bind(to: self.done) - .disposed(by: rx.disposeBag); - + .disposed(by: rx.disposeBag) let dismiss = PublishRelay() - input.doneTap.map{ _ in () } + input.doneTap.map { _ in () } .asObservable() .bind(to: dismiss) .disposed(by: rx.disposeBag) return Output( - groups: Driver.just([ SectionModel(model: "header", items: groupCellModels) ]), + groups: Driver.just([SectionModel(model: "header", items: groupCellModels)]), isShowAllGroups: isShowAllGroups.asDriver(onErrorDriveWith: .empty()), dismiss: dismiss.asDriver(onErrorDriveWith: .empty()) ) } - } - diff --git a/Controller/HomeViewController.swift b/Controller/HomeViewController.swift index dcac15d..644b7d4 100644 --- a/Controller/HomeViewController.swift +++ b/Controller/HomeViewController.swift @@ -6,14 +6,13 @@ // Copyright © 2018年 Fin. All rights reserved. // -import UIKit -import UserNotifications import Material import RxCocoa import RxDataSources +import UIKit +import UserNotifications class HomeViewController: BaseViewController { - let newButton: BKButton = { let btn = BKButton() btn.setImage(Icon.add, for: .normal) @@ -24,11 +23,11 @@ class HomeViewController: BaseViewController { let startButton: FABButton = { let button = FABButton(title: NSLocalizedString("RegisterDevice")) button.backgroundColor = Color.white - button.transition([ .scale(0.75) , .opacity(0)] ) + button.transition([.scale(0.75), .opacity(0)]) return button }() - let tableView :UITableView = { + let tableView: UITableView = { let tableView = UITableView() tableView.separatorStyle = .none tableView.backgroundColor = Color.grey.lighten4 @@ -44,12 +43,12 @@ class HomeViewController: BaseViewController { item: UIBarButtonItem(customView: newButton)) self.view.addSubview(self.tableView) - self.tableView.snp.makeConstraints { (make ) in + self.tableView.snp.makeConstraints { make in make.top.right.bottom.left.equalToSuperview() } self.view.addSubview(self.startButton) - self.startButton.snp.makeConstraints { (make) in + self.startButton.snp.makeConstraints { make in make.width.height.equalTo(150) make.centerX.equalToSuperview() make.centerY.equalToSuperview().offset(-50) @@ -57,12 +56,12 @@ class HomeViewController: BaseViewController { Client.shared.currentTabBarController? .tabBarItemDidClick - .filter{ $0 == .service } - .subscribe(onNext: {[weak self] index in + .filter { $0 == .service } + .subscribe(onNext: { [weak self] _ in self?.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true) }).disposed(by: self.rx.disposeBag) - } + override func bindViewModel() { guard let viewModel = self.viewModel as? HomeViewModel else { return @@ -72,96 +71,95 @@ class HomeViewController: BaseViewController { input: HomeViewModel.Input( addCustomServerTap: newButton.rx.tap.asDriver(), viewDidAppear: self.rx.methodInvoked(#selector(viewDidAppear(_:))) - .map{ _ in () } + .map { _ in () } .asDriver(onErrorDriveWith: .empty()), start: self.startButton.rx.tap.asDriver(), clientState: Client.shared.state.asDriver() ) ) - let dataSource = RxTableViewSectionedReloadDataSource> { (source, tableView, indexPath, item) -> UITableViewCell in - if let cell = tableView.dequeueReusableCell(withIdentifier: "\(PreviewCardCell.self)") as? PreviewCardCell{ + let dataSource = RxTableViewSectionedReloadDataSource> { _, tableView, _, item -> UITableViewCell in + if let cell = tableView.dequeueReusableCell(withIdentifier: "\(PreviewCardCell.self)") as? PreviewCardCell { cell.bindViewModel(model: item) return cell } return UITableViewCell() } - //标题 + // 标题 output.title .drive(self.navigationItem.rx.title) .disposed(by: rx.disposeBag) - //TableView数据源 + // TableView数据源 output.previews .drive(self.tableView.rx.items(dataSource: dataSource)) .disposed(by: rx.disposeBag) - //跳转到对应页面 + // 跳转到对应页面 output.push - .drive(onNext: {[weak self] viewModel in + .drive(onNext: { [weak self] viewModel in self?.pushViewModel(viewModel: viewModel) }) .disposed(by: rx.disposeBag) - //通过ping服务器,判断 clienState + // 通过ping服务器,判断 clienState output.clienStateChanged .drive(Client.shared.state) .disposed(by: rx.disposeBag) - //根据通知权限,设置是否隐藏注册按钮、显示示例预览列表 + // 根据通知权限,设置是否隐藏注册按钮、显示示例预览列表 output.tableViewHidden - .map{ !$0 } + .map { !$0 } .drive(self.tableView.rx.isHidden) .disposed(by: rx.disposeBag) output.tableViewHidden .drive(self.startButton.rx.isHidden) .disposed(by: rx.disposeBag) - //注册推送 + // 注册推送 output.registerForRemoteNotifications.drive(onNext: { UIApplication.shared.registerForRemoteNotifications() }) .disposed(by: rx.disposeBag) - //弹出提示 + // 弹出提示 output.showSnackbar - .drive(onNext: {[weak self] text in + .drive(onNext: { [weak self] text in self?.showSnackbar(text: text) }) .disposed(by: rx.disposeBag) - //startButton是否可点击 + // startButton是否可点击 output.startButtonEnable .drive(self.startButton.rx.isEnabled) .disposed(by: rx.disposeBag) - //复制文本 + // 复制文本 output.copy - .drive(onNext: {[weak self] text in + .drive(onNext: { [weak self] text in UIPasteboard.general.string = text self?.showSnackbar(text: NSLocalizedString("Copy")) }) .disposed(by: rx.disposeBag) - //预览 + // 预览 output.preview .drive(onNext: { url in UIApplication.shared.open(url, options: [:], completionHandler: nil) }) .disposed(by: rx.disposeBag) - //原样刷新 TableView + // 原样刷新 TableView output.reloadData - .drive(onNext: {[weak self] in + .drive(onNext: { [weak self] in self?.tableView.reloadData() }) .disposed(by: rx.disposeBag) - } - func pushViewModel(viewModel:ViewModel) { - var viewController:UIViewController? + func pushViewModel(viewModel: ViewModel) { + var viewController: UIViewController? if let viewModel = viewModel as? NewServerViewModel { viewController = NewServerViewController(viewModel: viewModel) } @@ -173,5 +171,4 @@ class HomeViewController: BaseViewController { self.navigationController?.pushViewController(viewController, animated: true) } } - } diff --git a/Controller/HomeViewModel.swift b/Controller/HomeViewModel.swift index 8de9e90..3db5af4 100644 --- a/Controller/HomeViewModel.swift +++ b/Controller/HomeViewModel.swift @@ -7,9 +7,9 @@ // import Foundation -import RxSwift import RxCocoa import RxDataSources +import RxSwift import SwiftyJSON import UserNotifications @@ -20,8 +20,9 @@ class HomeViewModel: ViewModel, ViewModelType { let start: Driver let clientState: Driver } + struct Output { - let previews: Driver<[SectionModel]> + let previews: Driver<[SectionModel]> let push: Driver let title: Driver let clienStateChanged: Driver @@ -34,44 +35,46 @@ class HomeViewModel: ViewModel, ViewModelType { let registerForRemoteNotifications: Driver } - let previews:[PreviewModel] = { - return [ + let previews: [PreviewModel] = { + [ PreviewModel( body: NSLocalizedString("CustomedNotificationContent"), - notice: NSLocalizedString("Notice1")), + notice: NSLocalizedString("Notice1") + ), PreviewModel( title: NSLocalizedString("CustomedNotificationTitle"), body: NSLocalizedString("CustomedNotificationContent"), - notice: NSLocalizedString("Notice2")), + notice: NSLocalizedString("Notice2") + ), PreviewModel( body: NSLocalizedString("notificationSound"), notice: NSLocalizedString("setSounds"), queryParameter: "sound=minuet", - moreInfo:NSLocalizedString("viewAllSounds"), + moreInfo: NSLocalizedString("viewAllSounds"), moreViewModel: SoundsViewModel() ), PreviewModel( body: NSLocalizedString("archiveNotificationMessageTitle"), notice: NSLocalizedString("archiveNotificationMessage"), queryParameter: "isArchive=1" - ), + ), PreviewModel( body: NSLocalizedString("notificationIcon"), notice: NSLocalizedString("notificationIconNotice"), queryParameter: "icon=https://day.app/assets/images/avatar.jpg", image: UIImage(named: "icon") - ), + ), PreviewModel( body: NSLocalizedString("messageGroup"), notice: NSLocalizedString("groupMessagesNotice"), queryParameter: "group=groupName", image: UIImage(named: "group") - ), + ), PreviewModel( body: "URL Test", notice: NSLocalizedString("urlParameter"), queryParameter: "url=https://www.baidu.com" - ), + ), PreviewModel( body: "Copy Test", notice: NSLocalizedString("copyParameter"), @@ -87,36 +90,35 @@ class HomeViewModel: ViewModel, ViewModelType { }() func transform(input: Input) -> Output { - let title = BehaviorRelay(value: URL(string: ServerManager.shared.currentAddress)?.host ?? "") let sectionModel = SectionModel( model: "previews", - items: previews.map { PreviewCardCellViewModel(previewModel: $0, clientState: input.clientState) }) + items: previews.map { PreviewCardCellViewModel(previewModel: $0, clientState: input.clientState) } + ) + // 点击跳转到添加自定义服务器 + let customServer = input.addCustomServerTap.map { NewServerViewModel() as ViewModel } - //点击跳转到添加自定义服务器 - let customServer = input.addCustomServerTap.map{ NewServerViewModel() as ViewModel } - - //如果更改了服务器地址,返回时也需更改 title + // 如果更改了服务器地址,返回时也需更改 title customServer - .flatMapLatest({ (model) -> Driver in - return (model as! NewServerViewModel).pop.asDriver(onErrorJustReturn: "") - }) + .flatMapLatest { model -> Driver in + (model as! NewServerViewModel).pop.asDriver(onErrorJustReturn: "") + } .drive(title) .disposed(by: rx.disposeBag) - //点击preview中的notice ,跳转到对应的页面 - let noticeTap = Driver.merge(sectionModel.items.map{ $0.noticeTap.asDriver(onErrorDriveWith: .empty()) }) + // 点击preview中的notice ,跳转到对应的页面 + let noticeTap = Driver.merge(sectionModel.items.map { $0.noticeTap.asDriver(onErrorDriveWith: .empty()) }) // 判断服务器状态 let clienState = input.viewDidAppear - .asObservable().flatMapLatest { _ -> Observable> in + .asObservable().flatMapLatest { _ -> Observable> in BarkApi.provider .request(.ping(baseURL: ServerManager.shared.currentAddress)) .filterResponseError() } - .map { (response) -> Client.ClienState in + .map { response -> Client.ClienState in switch response { case .failure: return .serverError @@ -125,40 +127,39 @@ class HomeViewModel: ViewModel, ViewModelType { } } - //第一次进入APP 查看通知权限设置 - let authorizationStatus = Single.create { (single) -> Disposable in - UNUserNotificationCenter.current().getNotificationSettings { (settings) in + // 第一次进入APP 查看通知权限设置 + let authorizationStatus = Single.create { single -> Disposable in + UNUserNotificationCenter.current().getNotificationSettings { settings in single(.success(settings.authorizationStatus)) } return Disposables.create() } - .map { $0 == .authorized} + .map { $0 == .authorized } - //点击注册按钮,请求通知权限 - let startRequestAuthorization = Single.create { (single) -> Disposable in + // 点击注册按钮,请求通知权限 + let startRequestAuthorization = Single.create { single -> Disposable in let center = UNUserNotificationCenter.current() - center.requestAuthorization(options: [.alert , .sound , .badge], completionHandler: {(_ granted: Bool, _ error: Error?) -> Void in + center.requestAuthorization(options: [.alert, .sound, .badge], completionHandler: { (_ granted: Bool, _: Error?) -> Void in single(.success(granted)) }) return Disposables.create() } .asObservable() - //根据通知权限,设置是否隐藏注册按钮、显示示例预览列表 + // 根据通知权限,设置是否隐藏注册按钮、显示示例预览列表 let tableViewHidden = authorizationStatus .asObservable() .concat(input.start - .asObservable() - .flatMapLatest{ startRequestAuthorization }) + .asObservable() + .flatMapLatest { startRequestAuthorization }) .asDriver(onErrorJustReturn: false) - let showSnackbar = PublishRelay() - //点击注册按钮后,如果不允许推送,弹出提示 + // 点击注册按钮后,如果不允许推送,弹出提示 tableViewHidden .skip(1) - .compactMap { (granted) -> String? in + .compactMap { granted -> String? in if !granted { return NSLocalizedString("AllowNotifications") } @@ -168,36 +169,35 @@ class HomeViewModel: ViewModel, ViewModelType { .bind(to: showSnackbar) .disposed(by: rx.disposeBag) - //点击注册按钮,如果用户允许推送,则通知 viewController 注册推送 + // 点击注册按钮,如果用户允许推送,则通知 viewController 注册推送 let registerForRemoteNotifications = tableViewHidden .skip(1) - .filter{ $0 } - .map{ _ in () } + .filter { $0 } + .map { _ in () } - //client state 变化时,发出相应错误提醒 + // client state 变化时,发出相应错误提醒 input.clientState.drive(onNext: { state in switch state { - case .ok: break; + case .ok: break case .serverError: showSnackbar.accept(NSLocalizedString("ServerError")) - default: break; + default: break } }) .disposed(by: rx.disposeBag) return Output( - previews:Driver.just([sectionModel]), - push: Driver.merge(customServer,noticeTap), + previews: Driver.just([sectionModel]), + push: Driver.merge(customServer, noticeTap), title: title.asDriver(), clienStateChanged: clienState.asDriver(onErrorDriveWith: .empty()), tableViewHidden: tableViewHidden, showSnackbar: showSnackbar.asDriver(onErrorDriveWith: .empty()), startButtonEnable: Driver.just(true), - copy: Driver.merge(sectionModel.items.map{ $0.copy.asDriver(onErrorDriveWith: .empty()) }), - preview: Driver.merge(sectionModel.items.map{ $0.preview.asDriver(onErrorDriveWith: .empty()) }), - reloadData: input.clientState.map{ _ in ()}, + copy: Driver.merge(sectionModel.items.map { $0.copy.asDriver(onErrorDriveWith: .empty()) }), + preview: Driver.merge(sectionModel.items.map { $0.preview.asDriver(onErrorDriveWith: .empty()) }), + reloadData: input.clientState.map { _ in () }, registerForRemoteNotifications: registerForRemoteNotifications ) } - } diff --git a/Controller/MessageListViewController.swift b/Controller/MessageListViewController.swift index 29086fd..f9aa2aa 100644 --- a/Controller/MessageListViewController.swift +++ b/Controller/MessageListViewController.swift @@ -6,29 +6,27 @@ // Copyright © 2020 Fin. All rights reserved. // -import UIKit import Material +import MJRefresh import RealmSwift import RxCocoa import RxDataSources -import MJRefresh import RxSwift +import UIKit -enum MessageDeleteType: Int{ +enum MessageDeleteType: Int { case lastHour = 0 case today case todayAndYesterday case allTime - var string: String{ - get { - return [ - NSLocalizedString("lastHour"), - NSLocalizedString("today"), - NSLocalizedString("todayAndYesterday"), - NSLocalizedString("allTime"), - ][self.rawValue] - } + var string: String { + return [ + NSLocalizedString("lastHour"), + NSLocalizedString("today"), + NSLocalizedString("todayAndYesterday"), + NSLocalizedString("allTime"), + ][self.rawValue] } } @@ -64,7 +62,7 @@ class MessageListViewController: BaseViewController { navigationItem.setBarButtonItems(items: [UIBarButtonItem(customView: deleteButton), UIBarButtonItem(customView: groupButton)], left: false) self.view.addSubview(tableView) - tableView.snp.makeConstraints { (make) in + tableView.snp.makeConstraints { make in make.edges.equalToSuperview() } tableView.rx.setDelegate(self).disposed(by: rx.disposeBag) @@ -74,13 +72,12 @@ class MessageListViewController: BaseViewController { // 点击tab按钮,回到顶部 Client.shared.currentTabBarController? .tabBarItemDidClick - .filter{ $0 == .messageHistory } - .subscribe(onNext: {[weak self] index in + .filter { $0 == .messageHistory } + .subscribe(onNext: { [weak self] _ in self?.scrollToTop() }).disposed(by: self.rx.disposeBag) - - //打开APP时,历史消息列表距离上次刷新超过1小时,则自动刷新一下 + // 打开APP时,历史消息列表距离上次刷新超过1小时,则自动刷新一下 var lastAutoRefreshdate = Date() NotificationCenter.default.rx .notification(UIApplication.willEnterForegroundNotification) @@ -92,11 +89,10 @@ class MessageListViewController: BaseViewController { } return false } - .subscribe(onNext: {[weak self] _ in + .subscribe(onNext: { [weak self] _ in self?.tableView.refreshControl?.sendActions(for: .valueChanged) self?.scrollToTop() }).disposed(by: rx.disposeBag) - } override func bindViewModel() { @@ -106,10 +102,10 @@ class MessageListViewController: BaseViewController { let batchDelete = deleteButton.rx .tap - .flatMapLatest { Void -> PublishRelay in + .flatMapLatest { _ -> PublishRelay in let relay = PublishRelay() - func alert(_ type:MessageDeleteType){ + func alert(_ type: MessageDeleteType) { let alertController = UIAlertController(title: nil, message: "\(NSLocalizedString("clearFrom"))\n\(type.string)", preferredStyle: .alert) alertController.addAction(UIAlertAction(title: NSLocalizedString("clear"), style: .destructive, handler: { _ in relay.accept(type) @@ -143,67 +139,65 @@ class MessageListViewController: BaseViewController { loadMore: tableView.mj_footer!.rx.refresh.asDriver(), itemDelete: tableView.rx.itemDeleted.asDriver(), itemSelected: tableView.rx.modelSelected(MessageTableViewCellViewModel.self).asDriver(), - delete:batchDelete.asDriver(onErrorDriveWith: .empty()), + delete: batchDelete.asDriver(onErrorDriveWith: .empty()), groupTap: groupButton.rx.tap.asDriver(), - searchText: navigationItem.searchController!.searchBar.rx.text.asObservable() - )) + searchText: navigationItem.searchController!.searchBar.rx.text.asObservable())) - //tableView 刷新状态 + // tableView 刷新状态 output.refreshAction .drive(tableView.rx.refreshAction) .disposed(by: rx.disposeBag) - //tableView 数据源 + // tableView 数据源 let dataSource = RxTableViewSectionedAnimatedDataSource( animationConfiguration: AnimationConfiguration( insertAnimation: .none, reloadAnimation: .none, deleteAnimation: .left), - configureCell:{ (source, tableView, indexPath, item) -> UITableViewCell in + configureCell: { _, tableView, _, item -> UITableViewCell in guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(MessageTableViewCell.self)") as? MessageTableViewCell else { - return UITableViewCell () + return UITableViewCell() } cell.bindViewModel(model: item) return cell }, canEditRowAtIndexPath: { _, _ in - return true + true }) output.messages .drive(tableView.rx.items(dataSource: dataSource)) .disposed(by: rx.disposeBag) - //message操作alert - output.alertMessage.drive(onNext: {[weak self] message in + // message操作alert + output.alertMessage.drive(onNext: { [weak self] message in self?.alertMessage(message: message) }).disposed(by: rx.disposeBag) - //点击message中的URL + // 点击message中的URL output.urlTap.drive(onNext: { url in - if ["http","https"].contains(url.scheme?.lowercased() ?? ""){ + if ["http", "https"].contains(url.scheme?.lowercased() ?? "") { self.navigationController?.present(BarkSFSafariViewController(url: url), animated: true, completion: nil) - } - else{ - UIApplication.shared.open(url, options: [:], completionHandler: nil) - } + } + else { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } }).disposed(by: rx.disposeBag) - //选择群组 + // 选择群组 output.groupFilter - .drive(onNext: {[weak self] groupModel in + .drive(onNext: { [weak self] groupModel in self?.navigationController?.present(BarkNavigationController(rootViewController: GroupFilterViewController(viewModel: groupModel)), animated: true, completion: nil) }).disposed(by: rx.disposeBag) - //标题 + // 标题 output.title .drive(self.navigationItem.rx.title).disposed(by: rx.disposeBag) - } - func alertMessage(message:String) { + func alertMessage(message: String) { let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - let copyAction = UIAlertAction(title: NSLocalizedString("Copy2"), style: .default, handler: {[weak self] - (alert: UIAlertAction) -> Void in + let copyAction = UIAlertAction(title: NSLocalizedString("Copy2"), style: .default, handler: { [weak self] + (_: UIAlertAction) -> Void in UIPasteboard.general.string = message self?.showSnackbar(text: NSLocalizedString("Copy")) }) @@ -216,7 +210,7 @@ class MessageListViewController: BaseViewController { self.navigationController?.present(alertController, animated: true, completion: nil) } - private func scrollToTop(){ + private func scrollToTop() { if self.tableView.visibleCells.count > 0 { self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true) } @@ -225,7 +219,7 @@ class MessageListViewController: BaseViewController { extension MessageListViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let action = UIContextualAction(style: .destructive, title: "删除") {[weak self] (action, sourceView, actionPerformed) in + let action = UIContextualAction(style: .destructive, title: "删除") { [weak self] _, _, actionPerformed in self?.tableView.dataSource?.tableView?(self!.tableView, commit: .delete, forRowAt: indexPath) actionPerformed(true) } @@ -235,14 +229,15 @@ extension MessageListViewController: UITableViewDelegate { } } -extension MessageListViewController: UISearchControllerDelegate{ +extension MessageListViewController: UISearchControllerDelegate { func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - if self.navigationItem.searchController?.searchBar.isFirstResponder == true{ + if self.navigationItem.searchController?.searchBar.isFirstResponder == true { self.navigationItem.searchController?.searchBar.resignFirstResponder() } } + func willDismissSearchController(_ searchController: UISearchController) { - if !searchController.searchBar.isFirstResponder{ + if !searchController.searchBar.isFirstResponder { /* searchBar 不在焦点时,点击搜索框右边的取消按钮时,不会触发 searchBar.rx.text 更改事件 searchBar.rx.text 将一直保留为最后的文本 diff --git a/Controller/MessageListViewModel.swift b/Controller/MessageListViewModel.swift index d712095..fa9cb13 100644 --- a/Controller/MessageListViewModel.swift +++ b/Controller/MessageListViewModel.swift @@ -7,12 +7,12 @@ // import Foundation -import RxSwift -import RxDataSources -import RxCocoa import RealmSwift +import RxCocoa +import RxDataSources +import RxSwift -class MessageListViewModel: ViewModel,ViewModelType { +class MessageListViewModel: ViewModel, ViewModelType { struct Input { var refresh: Driver var loadMore: Driver @@ -32,10 +32,10 @@ class MessageListViewModel: ViewModel,ViewModelType { var title: Driver } - private var results:Results? + private var results: Results? - //根据群组获取消息 - private func getResults(filterGroups:[String?], searchText:String?) -> Results? { + // 根据群组获取消息 + private func getResults(filterGroups: [String?], searchText: String?) -> Results? { if let realm = try? Realm() { var results = realm.objects(Message.self) .filter("isDeleted != true") @@ -60,7 +60,7 @@ class MessageListViewModel: ViewModel,ViewModelType { guard endIndex > startIndex else { return [] } - var messages:[Message] = [] + var messages: [Message] = [] for i in startIndex ..< endIndex { messages.append(result[i]) } @@ -70,12 +70,11 @@ class MessageListViewModel: ViewModel,ViewModelType { return [] } - func transform(input: Input) -> Output { - let alertMessage = input.itemSelected.map { (model) -> String in + let alertMessage = input.itemSelected.map { model -> String in let message = model.message - var copyContent:String = "" + var copyContent: String = "" if let title = message.title { copyContent += "\(title)\n" } @@ -97,45 +96,45 @@ class MessageListViewModel: ViewModel,ViewModelType { let refreshAction = BehaviorRelay(value: .none) // 切换群组 let filterGroups: BehaviorRelay<[String?]> = { - if let groups:[String?] = Settings["me.fin.filterGroups"] { + if let groups: [String?] = Settings["me.fin.filterGroups"] { return BehaviorRelay<[String?]>(value: groups) } return BehaviorRelay<[String?]>(value: []) }() // Message 转 MessageSection - func messagesToMessageSection(messages:[Message]) -> [MessageSection] { - let cellViewModels = messages.map({ (message) -> MessageTableViewCellViewModel in - return MessageTableViewCellViewModel(message: message) - }) + func messagesToMessageSection(messages: [Message]) -> [MessageSection] { + let cellViewModels = messages.map { message -> MessageTableViewCellViewModel in + MessageTableViewCellViewModel(message: message) + } return [MessageSection(header: "model", messages: cellViewModels)] } - //切换分组时,更新分组名 + // 切换分组时,更新分组名 filterGroups - .subscribe(onNext: {filterGroups in + .subscribe(onNext: { filterGroups in if filterGroups.count <= 0 { titleRelay.accept(NSLocalizedString("historyMessage")) } - else{ + else { titleRelay.accept(filterGroups.map { $0 ?? NSLocalizedString("default") }.joined(separator: " , ")) } }).disposed(by: rx.disposeBag) - //切换分组和更改搜索词时,更新数据源 + // 切换分组和更改搜索词时,更新数据源 Observable .combineLatest(filterGroups, input.searchText) - .subscribe(onNext: {[weak self] groups, searchText in + .subscribe(onNext: { [weak self] groups, searchText in self?.results = self?.getResults(filterGroups: groups, searchText: searchText) }).disposed(by: rx.disposeBag) - //切换分组和下拉刷新时,重新刷新列表 + // 切换分组和下拉刷新时,重新刷新列表 Observable .merge( - input.refresh.asObservable().map{ () }, - filterGroups.map{ _ in () }, - input.searchText.asObservable().map{ _ in () } + input.refresh.asObservable().map { () }, + filterGroups.map { _ in () }, + input.searchText.asObservable().map { _ in () } ) - .subscribe(onNext: {[weak self] in + .subscribe(onNext: { [weak self] in guard let strongSelf = self else { return } strongSelf.page = 0 messagesRelay.accept( @@ -146,27 +145,27 @@ class MessageListViewModel: ViewModel,ViewModelType { refreshAction.accept(.endRefresh) }).disposed(by: rx.disposeBag) - //加载更多 + // 加载更多 input.loadMore.asObservable() - .subscribe(onNext: {[weak self] in + .subscribe(onNext: { [weak self] in guard let strongSelf = self else { return } let messages = strongSelf.getNextPage() - let cellViewModels = messages.map({ (message) -> MessageTableViewCellViewModel in - return MessageTableViewCellViewModel(message: message) - }) + let cellViewModels = messages.map { message -> MessageTableViewCellViewModel in + MessageTableViewCellViewModel(message: message) + } refreshAction.accept(.endLoadmore) if var section = messagesRelay.value.first { section.messages.append(contentsOf: cellViewModels) messagesRelay.accept([section]) } - else{ + else { messagesRelay.accept([MessageSection(header: "model", messages: cellViewModels)]) } }).disposed(by: rx.disposeBag) - //删除message - input.itemDelete.drive(onNext: {[weak self] indexPath in + // 删除message + input.itemDelete.drive(onNext: { [weak self] indexPath in if var section = messagesRelay.value.first { if let realm = try? Realm() { try? realm.write { @@ -180,19 +179,19 @@ class MessageListViewModel: ViewModel,ViewModelType { }).disposed(by: rx.disposeBag) // cell 中点击 url。 - let urlTap = messagesRelay.flatMapLatest { (section) -> Observable in + let urlTap = messagesRelay.flatMapLatest { section -> Observable in if let section = section.first { - let taps = section.messages.compactMap { (model) -> Observable in - return model.urlTap.asObservable() + let taps = section.messages.compactMap { model -> Observable in + model.urlTap.asObservable() } return Observable.merge(taps) } return .empty() } - .compactMap { URL(string: $0) } //只处理正确的url + .compactMap { URL(string: $0) } // 只处理正确的url - //批量删除 - input.delete.drive(onNext: {[weak self] type in + // 批量删除 + input.delete.drive(onNext: { [weak self] type in guard let strongSelf = self else { return } var date = Date() @@ -211,7 +210,7 @@ class MessageListViewModel: ViewModel,ViewModelType { guard let messages = strongSelf.getResults(filterGroups: filterGroups.value, searchText: nil)?.filter("createDate >= %@", date) else { return } - try? realm.write{ + try? realm.write { for msg in messages { msg.isDeleted = true } @@ -223,31 +222,31 @@ class MessageListViewModel: ViewModel,ViewModelType { }).disposed(by: rx.disposeBag) - //群组筛选 - let groupFilter = input.groupTap.compactMap {() -> GroupFilterViewModel? in + // 群组筛选 + let groupFilter = input.groupTap.compactMap { () -> GroupFilterViewModel? in if let realm = try? Realm() { let groups = realm.objects(Message.self) .filter("isDeleted != true") .distinct(by: ["group"]) .value(forKeyPath: "group") as? [String?] - let groupModels = groups?.compactMap({ groupName -> GroupFilterModel in + let groupModels = groups?.compactMap { groupName -> GroupFilterModel in var check = true if filterGroups.value.count > 0 { check = filterGroups.value.contains(groupName) } return GroupFilterModel(name: groupName, checked: check) - }) + } if let models = groupModels { let viewModel = GroupFilterViewModel(groups: models) - //保存选择的 group + // 保存选择的 group viewModel.done.subscribe(onNext: { filterGroups in Settings["me.fin.filterGroups"] = filterGroups }).disposed(by: viewModel.rx.disposeBag) - //将选择绑定到当前页面 + // 将选择绑定到当前页面 viewModel.done .bind(to: filterGroups) .disposed(by: viewModel.rx.disposeBag) diff --git a/Controller/MessageSettingsViewController.swift b/Controller/MessageSettingsViewController.swift index fed5e96..ed073c1 100644 --- a/Controller/MessageSettingsViewController.swift +++ b/Controller/MessageSettingsViewController.swift @@ -6,9 +6,9 @@ // Copyright © 2020 Fin. All rights reserved. // -import UIKit import Material import RxDataSources +import UIKit class MessageSettingsViewController: BaseViewController { let tableView: UITableView = { let tableView = UITableView() @@ -22,14 +22,16 @@ class MessageSettingsViewController: BaseViewController { return tableView }() + override func makeUI() { self.title = NSLocalizedString("settings") self.view.addSubview(tableView) - tableView.snp.makeConstraints { (make) in + tableView.snp.makeConstraints { make in make.edges.equalToSuperview() } } + override func bindViewModel() { guard let viewModel = self.viewModel as? MessageSettingsViewModel else { return @@ -40,9 +42,9 @@ class MessageSettingsViewController: BaseViewController { ) ) - let dataSource = RxTableViewSectionedReloadDataSource> { (source, tableView, indexPath, item) -> UITableViewCell in + let dataSource = RxTableViewSectionedReloadDataSource> { _, tableView, _, item -> UITableViewCell in switch item { - case .label(let text): + case let .label(text): if let cell = tableView.dequeueReusableCell(withIdentifier: "\(LabelCell.self)") as? LabelCell { cell.textLabel?.text = text return cell @@ -51,12 +53,12 @@ class MessageSettingsViewController: BaseViewController { if let cell = tableView.dequeueReusableCell(withIdentifier: "\(iCloudStatusCell.self)") { return cell } - case .archiveSetting(let viewModel): + case let .archiveSetting(viewModel): if let cell = tableView.dequeueReusableCell(withIdentifier: "\(ArchiveSettingCell.self)") as? ArchiveSettingCell { cell.bindViewModel(model: viewModel) return cell } - case let .detail(title,text,textColor,_): + case let .detail(title, text, textColor, _): if let cell = tableView.dequeueReusableCell(withIdentifier: "\(DetailTextCell.self)") as? DetailTextCell { cell.textLabel?.text = title cell.detailTextLabel?.text = text @@ -78,10 +80,8 @@ class MessageSettingsViewController: BaseViewController { .drive(tableView.rx.items(dataSource: dataSource)) .disposed(by: rx.disposeBag) - output.openUrl.drive {[weak self] url in + output.openUrl.drive { [weak self] url in self?.navigationController?.present(BarkSFSafariViewController(url: url), animated: true, completion: nil) }.disposed(by: rx.disposeBag) - } - } diff --git a/Controller/MessageSettingsViewModel.swift b/Controller/MessageSettingsViewModel.swift index 1229c50..07137ad 100644 --- a/Controller/MessageSettingsViewModel.swift +++ b/Controller/MessageSettingsViewModel.swift @@ -7,22 +7,23 @@ // import Foundation -import RxSwift +import Material import RxCocoa import RxDataSources -import Material +import RxSwift class MessageSettingsViewModel: ViewModel, ViewModelType { struct Input { var itemSelected: Driver } + struct Output { - var settings:Driver<[SectionModel]> - var openUrl:Driver + var settings: Driver<[SectionModel]> + var openUrl: Driver } + func transform(input: Input) -> Output { - - let settings:[MessageSettingItem] = { + let settings: [MessageSettingItem] = { var settings = [MessageSettingItem]() settings.append(.label(text: "iCloud")) settings.append(.iCloudStatus) @@ -32,41 +33,41 @@ class MessageSettingsViewModel: ViewModel, ViewModelType { settings.append(.label(text: NSLocalizedString("archiveNote"))) if let infoDict = Bundle.main.infoDictionary, - let runId = infoDict["GitHub Run Id"] as? String{ + let runId = infoDict["GitHub Run Id"] as? String + { settings.append(.label(text: NSLocalizedString("buildInfo"))) settings.append(.detail( - title: "Github Run Id", - text:"\(runId)", - textColor: Color.grey.darken2, - url: URL(string: "https://github.com/Finb/Bark/actions/runs/\(runId)"))) + title: "Github Run Id", + text: "\(runId)", + textColor: Color.grey.darken2, + url: URL(string: "https://github.com/Finb/Bark/actions/runs/\(runId)"))) settings.append(.label(text: NSLocalizedString("buildDesc"))) } - settings.append(.label(text: NSLocalizedString("other"))) settings.append(.detail( - title: NSLocalizedString("faq"), - text: nil, - textColor: nil, - url: URL(string: "https://day.app/2021/06/barkfaq/"))) + title: NSLocalizedString("faq"), + text: nil, + textColor: nil, + url: URL(string: "https://day.app/2021/06/barkfaq/"))) settings.append(.spacer(height: 0.5, color: Color.grey.lighten4)) settings.append(.detail( - title: NSLocalizedString("appSC"), - text: nil, - textColor: nil, - url: URL(string: "https://github.com/Finb/Bark"))) + title: NSLocalizedString("appSC"), + text: nil, + textColor: nil, + url: URL(string: "https://github.com/Finb/Bark"))) settings.append(.spacer(height: 0.5, color: Color.grey.lighten4)) settings.append(.detail( - title: NSLocalizedString("backendSC"), - text: nil, - textColor: nil, - url: URL(string: "https://github.com/Finb/bark-server"))) + title: NSLocalizedString("backendSC"), + text: nil, + textColor: nil, + url: URL(string: "https://github.com/Finb/bark-server"))) return settings }() - settings.compactMap { (item) -> ArchiveSettingCellViewModel? in + settings.compactMap { item -> ArchiveSettingCellViewModel? in if case let MessageSettingItem.archiveSetting(viewModel) = item { return viewModel } @@ -74,7 +75,7 @@ class MessageSettingsViewModel: ViewModel, ViewModelType { } .first? .on - .subscribe(onNext: { (on) in + .subscribe(onNext: { on in ArchiveSettingManager.shared.isArchive = on }).disposed(by: rx.disposeBag) @@ -85,25 +86,22 @@ class MessageSettingsViewModel: ViewModel, ViewModelType { return nil } - return Output( settings: Driver<[SectionModel]> - .just([SectionModel(model: "model", items: settings)]), - openUrl: openUrl - ) + .just([SectionModel(model: "model", items: settings)]), + openUrl: openUrl) } - } enum MessageSettingItem { // 普通标题标签 - case label(text:String) + case label(text: String) // iCloud 状态 case iCloudStatus // 默认保存 - case archiveSetting(viewModel:ArchiveSettingCellViewModel) + case archiveSetting(viewModel: ArchiveSettingCellViewModel) // 带 详细按钮的 文本cell - case detail(title:String?, text:String?, textColor:UIColor?, url:URL?) + case detail(title: String?, text: String?, textColor: UIColor?, url: URL?) // 分隔线 - case spacer(height:CGFloat, color:UIColor?) + case spacer(height: CGFloat, color: UIColor?) } diff --git a/Controller/NewServerViewController.swift b/Controller/NewServerViewController.swift index e24da89..2b96a69 100644 --- a/Controller/NewServerViewController.swift +++ b/Controller/NewServerViewController.swift @@ -6,22 +6,21 @@ // Copyright © 2018 Fin. All rights reserved. // -import UIKit import Material -import SnapKit -import SafariServices -import RxSwift import RxCocoa +import RxSwift +import SafariServices +import SnapKit +import UIKit class NewServerViewController: BaseViewController { - - let addressTextField : TextField = { + let addressTextField: TextField = { let textField = TextField() textField.keyboardType = .URL textField.placeholder = NSLocalizedString("ServerAddress") textField.detail = NSLocalizedString("ServerExample") - textField.transition([ .scale(0.85) , .opacity(0)] ) - textField.detailLabel.transition([ .scale(0.85) , .opacity(0)] ) + textField.transition([.scale(0.85), .opacity(0)]) + textField.detailLabel.transition([.scale(0.85), .opacity(0)]) return textField }() @@ -30,7 +29,7 @@ class NewServerViewController: BaseViewController { label.text = NSLocalizedString("DeploymentDocuments") label.textColor = Color.blue.base label.font = UIFont.systemFont(ofSize: 12) - label.transition([ .scale(0.85) , .opacity(0), .translate(x: 50)] ) + label.transition([.scale(0.85), .opacity(0), .translate(x: 50)]) label.isUserInteractionEnabled = true label.addGestureRecognizer(UITapGestureRecognizer()) return label @@ -52,35 +51,34 @@ class NewServerViewController: BaseViewController { .top(kNavigationHeight + 40).left(10).right(10) self.view.addSubview(noticeLabel) - noticeLabel.snp.makeConstraints { (make) in + noticeLabel.snp.makeConstraints { make in make.top.equalTo(self.addressTextField.snp.bottom).offset(40) make.left.equalTo(self.addressTextField) } } + override func bindViewModel() { guard let viewModel = self.viewModel as? NewServerViewModel else { return } let noticeTap = noticeLabel.gestureRecognizers!.first!.rx .event - .map({ (_) -> () in - return () - }) + .map { _ -> () in + () + } .asDriver(onErrorJustReturn: ()) let done = doneButton.rx.tap - .map({[weak self] in - return self?.addressTextField.text ?? "" - }) + .map { [weak self] in + self?.addressTextField.text ?? "" + } .asDriver(onErrorDriveWith: .empty()) let viewDidAppear = rx .methodInvoked(#selector(viewDidAppear(_:))) - .map{ _ in () } + .map { _ in () } .asDriver(onErrorDriveWith: .empty()) - - let output = viewModel.transform( input: NewServerViewModel.Input( noticeClick: noticeTap, @@ -88,36 +86,34 @@ class NewServerViewController: BaseViewController { viewDidAppear: viewDidAppear )) - //键盘显示与隐藏 + // 键盘显示与隐藏 output.showKeyboard.drive(onNext: { [weak self] show in if show { _ = self?.addressTextField.becomeFirstResponder() } - else{ + else { self?.addressTextField.resignFirstResponder() } }).disposed(by: rx.disposeBag) - //点击教程 - output.notice.drive(onNext: {[weak self] url in + // 点击教程 + output.notice.drive(onNext: { [weak self] url in self?.navigationController?.present(BarkSFSafariViewController(url: url), animated: true, completion: nil) }).disposed(by: rx.disposeBag) - //URL文本框文本 + // URL文本框文本 output.urlText .drive(self.addressTextField.rx.text) .disposed(by: rx.disposeBag) - //退出页面 - output.pop.drive(onNext: {[weak self] _ in + // 退出页面 + output.pop.drive(onNext: { [weak self] _ in self?.navigationController?.popViewController(animated: true) }).disposed(by: rx.disposeBag) - //弹出提示文本 - output.showSnackbar.drive(onNext: {[weak self] text in + // 弹出提示文本 + output.showSnackbar.drive(onNext: { [weak self] text in self?.showSnackbar(text: text) }).disposed(by: rx.disposeBag) - } - } diff --git a/Controller/NewServerViewModel.swift b/Controller/NewServerViewModel.swift index 581fb13..71033bb 100644 --- a/Controller/NewServerViewModel.swift +++ b/Controller/NewServerViewModel.swift @@ -7,14 +7,14 @@ // import Foundation -import RxSwift -import RxCocoa -import SwiftyJSON import Moya +import RxCocoa +import RxSwift +import SwiftyJSON class NewServerViewModel: ViewModel, ViewModelType { struct Input { - var noticeClick:Driver + var noticeClick: Driver var done: Driver var viewDidAppear: Driver } @@ -27,23 +27,21 @@ class NewServerViewModel: ViewModel, ViewModelType { var pop: Driver } - private var url: String = "" let pop = PublishRelay() func transform(input: Input) -> Output { - let showKeyboard = PublishRelay() let urlText = PublishRelay() let showSnackbar = PublishRelay() let notice = input.noticeClick - .map{ URL(string: "https://day.app/2018/06/bark-server-document/")! } + .map { URL(string: "https://day.app/2018/06/bark-server-document/")! } .asDriver() input.viewDidAppear - .map{ "https://" } + .map { "https://" } .asObservable() .take(1) .subscribe(onNext: { text in @@ -53,7 +51,7 @@ class NewServerViewModel: ViewModel, ViewModelType { input.done .asObservable() - .flatMapLatest {[weak self] (url) -> Observable> in + .flatMapLatest { [weak self] url -> Observable> in showKeyboard.accept(false) if let _ = URL(string: url) { guard let strongSelf = self else { return .empty() } @@ -67,7 +65,7 @@ class NewServerViewModel: ViewModel, ViewModelType { return .empty() } } - .subscribe(onNext: {[weak self] response in + .subscribe(onNext: { [weak self] response in guard let strongSelf = self else { return } switch response { case .success: diff --git a/Controller/SoundsViewController.swift b/Controller/SoundsViewController.swift index be6620d..f10658a 100644 --- a/Controller/SoundsViewController.swift +++ b/Controller/SoundsViewController.swift @@ -6,14 +6,14 @@ // Copyright © 2020 Fin. All rights reserved. // -import UIKit -import Material import AVKit +import Material +import UIKit -import RxSwift +import NSObject_Rx import RxCocoa import RxDataSources -import NSObject_Rx +import RxSwift class SoundsViewController: BaseViewController { let tableView: UITableView = { @@ -22,12 +22,12 @@ class SoundsViewController: BaseViewController { tableView.register(SoundCell.self, forCellReuseIdentifier: "\(SoundCell.self)") return tableView }() - + override func makeUI() { self.title = NSLocalizedString("notificationSound") self.view.addSubview(self.tableView) - self.tableView.snp.makeConstraints { (make) in + self.tableView.snp.makeConstraints { make in make.edges.equalToSuperview() } @@ -40,35 +40,36 @@ class SoundsViewController: BaseViewController { return header }() } + override func bindViewModel() { guard let viewModel = viewModel as? SoundsViewModel else { return } - + let output = viewModel.transform( input: SoundsViewModel.Input(soundSelected: self.tableView.rx - .modelSelected(SoundCellViewModel.self) - .asDriver())) - - let dataSource = RxTableViewSectionedReloadDataSource> { (source, tableView, indexPath, item) -> UITableViewCell in + .modelSelected(SoundCellViewModel.self) + .asDriver())) + + let dataSource = RxTableViewSectionedReloadDataSource> { _, tableView, _, item -> UITableViewCell in guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(SoundCell.self)") as? SoundCell else { return UITableViewCell() } cell.bindViewModel(model: item) return cell } - + output.audios .bind(to: tableView.rx.items(dataSource: dataSource)) .disposed(by: rx.disposeBag) - + output.copyNameAction.drive(onNext: { name in UIPasteboard.general.string = name.trimmingCharacters(in: .whitespacesAndNewlines) self.navigationController?.showSnackbar(text: NSLocalizedString("Copy")) }).disposed(by: rx.disposeBag) - + output.playAction.drive(onNext: { url in - var soundID:SystemSoundID = 0 + var soundID: SystemSoundID = 0 AudioServicesCreateSystemSoundID(url, &soundID) AudioServicesPlaySystemSoundWithCompletion(soundID) { AudioServicesDisposeSystemSoundID(soundID) diff --git a/Controller/SoundsViewModel.swift b/Controller/SoundsViewModel.swift index 18eab12..45890ad 100644 --- a/Controller/SoundsViewModel.swift +++ b/Controller/SoundsViewModel.swift @@ -6,19 +6,19 @@ // Copyright © 2020 Fin. All rights reserved. // -import Foundation -import RxSwift -import RxCocoa import AVKit +import Foundation +import RxCocoa import RxDataSources +import RxSwift -class SoundsViewModel:ViewModel,ViewModelType { - +class SoundsViewModel: ViewModel, ViewModelType { struct Input { - var soundSelected:Driver + var soundSelected: Driver } + struct Output { - var audios:Observable<[ SectionModel]> + var audios: Observable<[SectionModel]> var copyNameAction: Driver var playAction: Driver } @@ -26,15 +26,15 @@ class SoundsViewModel:ViewModel,ViewModelType { func transform(input: Input) -> Output { let models = { () -> [AVURLAsset] in var urls = Bundle.main.urls(forResourcesWithExtension: "caf", subdirectory: nil) ?? [] - urls.sort { (u1, u2) -> Bool in + urls.sort { u1, u2 -> Bool in u1.lastPathComponent.localizedStandardCompare(u2.lastPathComponent) == ComparisonResult.orderedAscending } - let audios = urls.map { (url) -> AVURLAsset in + let audios = urls.map { url -> AVURLAsset in let asset = AVURLAsset(url: url) return asset } return audios - }().map { SoundCellViewModel(model: $0 ) } + }().map { SoundCellViewModel(model: $0) } let copyAction = Driver.merge( models.map { $0.copyNameAction.asDriver(onErrorDriveWith: .empty()) } @@ -43,8 +43,7 @@ class SoundsViewModel:ViewModel,ViewModelType { return Output( audios: Observable.just([SectionModel(model: "model", items: models)]), copyNameAction: copyAction, - playAction: input.soundSelected.map{ $0.model.url as CFURL } + playAction: input.soundSelected.map { $0.model.url as CFURL } ) } - } diff --git a/Intent/IntentHandler.swift b/Intent/IntentHandler.swift index 35c2e86..5e1b7aa 100644 --- a/Intent/IntentHandler.swift +++ b/Intent/IntentHandler.swift @@ -18,7 +18,6 @@ import Intents // "Search for messages in " class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling { - override func handler(for intent: INIntent) -> Any { // This is the default implementation. If you want different objects to handle different intents, // you can override this and return the handler you want for that particular intent. @@ -31,7 +30,6 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag // Implement resolution methods to provide additional information about your intent (optional). func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) { if let recipients = intent.recipients { - // If no recipients were provided we'll need to prompt for a value. if recipients.count == 0 { completion([INSendMessageRecipientResolutionResult.needsValue()]) @@ -42,7 +40,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag for recipient in recipients { let matchingContacts = [recipient] // Implement your contact matching logic here to create an array of matching contacts switch matchingContacts.count { - case 2 ... Int.max: + case 2 ... Int.max: // We need Siri's help to ask user to pick one from the matches. resolutionResults += [INSendMessageRecipientResolutionResult.disambiguation(with: matchingContacts)] @@ -56,7 +54,6 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag default: break - } } completion(resolutionResults) @@ -107,9 +104,9 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag identifier: "identifier", content: "I am so excited about SiriKit!", dateSent: Date(), - sender: INPerson(personHandle: INPersonHandle(value: "sarah@example.com", type: .emailAddress), nameComponents: nil, displayName: "Sarah", image: nil, contactIdentifier: nil, customIdentifier: nil), - recipients: [INPerson(personHandle: INPersonHandle(value: "+1-415-555-5555", type: .phoneNumber), nameComponents: nil, displayName: "John", image: nil, contactIdentifier: nil, customIdentifier: nil)] - )] + sender: INPerson(personHandle: INPersonHandle(value: "sarah@example.com", type: .emailAddress), nameComponents: nil, displayName: "Sarah", image: nil, contactIdentifier: nil, customIdentifier: nil), + recipients: [INPerson(personHandle: INPersonHandle(value: "+1-415-555-5555", type: .phoneNumber), nameComponents: nil, displayName: "John", image: nil, contactIdentifier: nil, customIdentifier: nil)] + )] completion(response) } diff --git a/Model/Message.swift b/Model/Message.swift index 186da1f..b9337bf 100644 --- a/Model/Message.swift +++ b/Model/Message.swift @@ -6,25 +6,26 @@ // Copyright © 2020 Fin. All rights reserved. // -import UIKit -import RealmSwift import IceCream +import RealmSwift +import UIKit class Message: Object { @objc dynamic var id = NSUUID().uuidString - @objc dynamic var title:String? - @objc dynamic var body:String? - @objc dynamic var url:String? - @objc dynamic var group:String? - @objc dynamic var createDate:Date? - + @objc dynamic var title: String? + @objc dynamic var body: String? + @objc dynamic var url: String? + @objc dynamic var group: String? + @objc dynamic var createDate: Date? + // 设置为 true 后,将被IceCream自动清理 @objc dynamic var isDeleted = false - + override class func primaryKey() -> String? { return "id" } + override class func indexedProperties() -> [String] { - return ["group","createDate"] + return ["group", "createDate"] } } diff --git a/Model/PreviewModel.swift b/Model/PreviewModel.swift index aacccaf..820c271 100644 --- a/Model/PreviewModel.swift +++ b/Model/PreviewModel.swift @@ -10,24 +10,24 @@ import Foundation import UIKit class PreviewModel: NSObject { - var title:String? - var body:String? - var category:String? - var notice:String? - var queryParameter:String? - var image:UIImage? - var moreInfo:String? - var moreViewModel:ViewModel? - - init(title:String? = nil, - body:String? = nil, - category:String? = nil, - notice:String? = nil, - queryParameter:String? = nil, - image:UIImage? = nil, - moreInfo:String? = nil, - moreViewModel:ViewModel? = nil - ) { + var title: String? + var body: String? + var category: String? + var notice: String? + var queryParameter: String? + var image: UIImage? + var moreInfo: String? + var moreViewModel: ViewModel? + + init(title: String? = nil, + body: String? = nil, + category: String? = nil, + notice: String? = nil, + queryParameter: String? = nil, + image: UIImage? = nil, + moreInfo: String? = nil, + moreViewModel: ViewModel? = nil) + { self.title = title self.body = body self.category = category diff --git a/NotificationServiceExtension/NotificationService.swift b/NotificationServiceExtension/NotificationService.swift index 407cc9e..a9f7e0d 100644 --- a/NotificationServiceExtension/NotificationService.swift +++ b/NotificationServiceExtension/NotificationService.swift @@ -6,75 +6,74 @@ // Copyright © 2018 Fin. All rights reserved. // -import UIKit -import UserNotifications -import RealmSwift +import Intents import Kingfisher import MobileCoreServices -import Intents +import RealmSwift +import UIKit +import UserNotifications class NotificationService: UNNotificationServiceExtension { + var contentHandler: ((UNNotificationContent) -> ())? - var contentHandler: ((UNNotificationContent) -> Void)? - - lazy var realm:Realm? = { + lazy var realm: Realm? = { let groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bark") let fileUrl = groupUrl?.appendingPathComponent("bark.realm") let config = Realm.Configuration( fileURL: fileUrl, schemaVersion: 13, - migrationBlock: { migration, oldSchemaVersion in + migrationBlock: { _, oldSchemaVersion in // We haven’t migrated anything yet, so oldSchemaVersion == 0 - if (oldSchemaVersion < 1) { + if oldSchemaVersion < 1 { // Nothing to do! // Realm will automatically detect new properties and removed properties // And will update the schema on disk automatically } - }) + } + ) // Tell Realm to use this new configuration object for the default Realm Realm.Configuration.defaultConfiguration = config return try? Realm() }() - /// 自动保存推送 /// - Parameters: /// - userInfo: 推送参数 /// - bestAttemptContentBody: 推送body,如果用户`没有指定要复制的值` ,默认复制 `推送正文` - fileprivate func autoCopy(_ userInfo: [AnyHashable : Any], defaultCopy: String) { + fileprivate func autoCopy(_ userInfo: [AnyHashable: Any], defaultCopy: String) { if userInfo["autocopy"] as? String == "1" - || userInfo["automaticallycopy"] as? String == "1"{ + || userInfo["automaticallycopy"] as? String == "1" + { if let copy = userInfo["copy"] as? String { UIPasteboard.general.string = copy } - else{ + else { UIPasteboard.general.string = defaultCopy } } } - /// 保存推送 /// - Parameter userInfo: 推送参数 /// 如果用户携带了 `isarchive` 参数,则以 `isarchive` 参数值为准 /// 否则,以用户`应用内设置`为准 - fileprivate func archive(_ userInfo: [AnyHashable : Any]) { - var isArchive:Bool? - if let archive = userInfo["isarchive"] as? String{ + fileprivate func archive(_ userInfo: [AnyHashable: Any]) { + var isArchive: Bool? + if let archive = userInfo["isarchive"] as? String { isArchive = archive == "1" ? true : false } if isArchive == nil { isArchive = ArchiveSettingManager.shared.isArchive } - let alert = (userInfo["aps"] as? [String:Any])?["alert"] as? [String:Any] + let alert = (userInfo["aps"] as? [String: Any])?["alert"] as? [String: Any] let title = alert?["title"] as? String let body = alert?["body"] as? String let url = userInfo["url"] as? String let group = userInfo["group"] as? String - if (isArchive == true){ - try? realm?.write{ + if isArchive == true { + try? realm?.write { let message = Message() message.title = title message.body = body @@ -86,22 +85,21 @@ class NotificationService: UNNotificationServiceExtension { } } - /// 下载推送图片 /// - Parameters: /// - userInfo: 推送参数 /// - bestAttemptContent: 推送content /// - complection: 下载图片完毕后的回调函数 - fileprivate func downloadImage(_ imageUrl:String, complection: @escaping (_ imageFileUrl:String?) -> () ) { + fileprivate func downloadImage(_ imageUrl: String, complection: @escaping (_ imageFileUrl: String?) -> ()) { guard let groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bark"), - let cache = try? ImageCache(name: "shared",cacheDirectoryURL: groupUrl), + let cache = try? ImageCache(name: "shared", cacheDirectoryURL: groupUrl), let imageResource = URL(string: imageUrl) else { complection(nil) return } - func downloadFinished(){ + func downloadFinished() { let cacheFileUrl = cache.cachePath(forKey: imageResource.cacheKey) complection(cacheFileUrl) } @@ -119,7 +117,7 @@ class NotificationService: UNNotificationServiceExtension { return } // 缓存图片 - cache.storeToDisk(result.originalData, forKey: imageResource.cacheKey, expiration: StorageExpiration.never) { r in + cache.storeToDisk(result.originalData, forKey: imageResource.cacheKey, expiration: StorageExpiration.never) { _ in downloadFinished() } } @@ -130,14 +128,15 @@ class NotificationService: UNNotificationServiceExtension { /// - bestAttemptContent: 推送content /// - complection: 下载图片完毕后的回调函数 fileprivate func setImage(content bestAttemptContent: UNMutableNotificationContent, - complection: @escaping (_ content:UNMutableNotificationContent) -> () ) { + complection: @escaping (_ content: UNMutableNotificationContent) -> ()) + { let userInfo = bestAttemptContent.userInfo guard let imageUrl = userInfo["image"] as? String else { complection(bestAttemptContent) return } - func finished(_ imageFileUrl: String?){ + func finished(_ imageFileUrl: String?) { guard let imageFileUrl = imageFileUrl else { complection(bestAttemptContent) return @@ -146,13 +145,15 @@ class NotificationService: UNNotificationServiceExtension { // 将图片缓存复制一份,推送使用完后会自动删除,但图片缓存需要留着以后在历史记录里查看 try? FileManager.default.copyItem( at: URL(fileURLWithPath: imageFileUrl), - to: copyDestUrl) + to: copyDestUrl + ) - if let attachment = try? UNNotificationAttachment( + if let attachment = try? UNNotificationAttachment( identifier: "image", url: copyDestUrl, - options: [UNNotificationAttachmentOptionsTypeHintKey : kUTTypePNG]){ - bestAttemptContent.attachments = [ attachment ] + options: [UNNotificationAttachmentOptionsTypeHintKey: kUTTypePNG] + ) { + bestAttemptContent.attachments = [attachment] } complection(bestAttemptContent) } @@ -165,7 +166,8 @@ class NotificationService: UNNotificationServiceExtension { /// - bestAttemptContent: 推送 content /// - complection: 设置完成后的回调参数 fileprivate func setIcon(content bestAttemptContent: UNMutableNotificationContent, - complection: @escaping (_ content:UNMutableNotificationContent) -> () ) { + complection: @escaping (_ content: UNMutableNotificationContent) -> ()) + { if #available(iOSApplicationExtension 15.0, *) { let userInfo = bestAttemptContent.userInfo guard let imageUrl = userInfo["icon"] as? String else { @@ -173,7 +175,7 @@ class NotificationService: UNNotificationServiceExtension { return } - func finished(_ imageFileUrl: String?){ + func finished(_ imageFileUrl: String?) { guard let imageFileUrl = imageFileUrl else { complection(bestAttemptContent) return @@ -224,7 +226,8 @@ class NotificationService: UNNotificationServiceExtension { do { let content = try bestAttemptContent.updating(from: intent) as! UNMutableNotificationContent complection(content) - } catch { + } + catch { // Handle error } @@ -233,13 +236,12 @@ class NotificationService: UNNotificationServiceExtension { downloadImage(imageUrl, complection: finished) } - else{ + else { complection(bestAttemptContent) } } - override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { - + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> ()) { self.contentHandler = contentHandler guard let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) else { contentHandler(request.content) diff --git a/View/ArchiveSettingCell.swift b/View/ArchiveSettingCell.swift index 6671be5..ca88a15 100644 --- a/View/ArchiveSettingCell.swift +++ b/View/ArchiveSettingCell.swift @@ -13,22 +13,25 @@ class ArchiveSettingCell: BaseTableViewCell { let btn = UISwitch() return btn }() + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.selectionStyle = .none - + self.textLabel?.text = NSLocalizedString("defaultArchiveSettings") - + contentView.addSubview(switchButton) - switchButton.snp.makeConstraints { (make) in + switchButton.snp.makeConstraints { make in make.right.equalToSuperview().offset(-16) make.centerY.equalToSuperview() } } + + @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func bindViewModel(model: ViewModel) { super.bindViewModel(model: model) guard let viewModel = model as? ArchiveSettingCellViewModel else { @@ -38,4 +41,3 @@ class ArchiveSettingCell: BaseTableViewCell { .disposed(by: rx.reuseBag) } } - diff --git a/View/ArchiveSettingCellViewModel.swift b/View/ArchiveSettingCellViewModel.swift index 18d21f7..a93e3de 100644 --- a/View/ArchiveSettingCellViewModel.swift +++ b/View/ArchiveSettingCellViewModel.swift @@ -10,7 +10,7 @@ import Foundation import RxCocoa class ArchiveSettingCellViewModel: ViewModel { var on: BehaviorRelay - init(on:Bool) { + init(on: Bool) { self.on = BehaviorRelay(value: on) super.init() } diff --git a/View/BKButton.swift b/View/BKButton.swift index e47bbf7..c48cdae 100644 --- a/View/BKButton.swift +++ b/View/BKButton.swift @@ -8,25 +8,25 @@ import UIKit -protocol AlignmentRectInsetsOverridable:AnyObject { - var alignmentRectInsetsOverride: UIEdgeInsets? {get set} -} -protocol HitTestSlopable:AnyObject { - var hitTestSlop: UIEdgeInsets {get set} +protocol AlignmentRectInsetsOverridable: AnyObject { + var alignmentRectInsetsOverride: UIEdgeInsets? { get set } } -class BKButton: UIButton, HitTestSlopable,AlignmentRectInsetsOverridable { - - var hitTestSlop:UIEdgeInsets = UIEdgeInsets.zero +protocol HitTestSlopable: AnyObject { + var hitTestSlop: UIEdgeInsets { get set } +} + +class BKButton: UIButton, HitTestSlopable, AlignmentRectInsetsOverridable { + var hitTestSlop = UIEdgeInsets.zero override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { if hitTestSlop == UIEdgeInsets.zero { - return super.point(inside: point, with:event) + return super.point(inside: point, with: event) } - else{ + else { return self.bounds.inset(by: hitTestSlop).contains(point) } } - + var alignmentRectInsetsOverride: UIEdgeInsets? override var alignmentRectInsets: UIEdgeInsets { return alignmentRectInsetsOverride ?? super.alignmentRectInsets diff --git a/View/BKLabel.swift b/View/BKLabel.swift index 56c13f7..70a8d57 100644 --- a/View/BKLabel.swift +++ b/View/BKLabel.swift @@ -9,16 +9,14 @@ import UIKit class BKLabel: UILabel { + var hitTestSlop = UIEdgeInsets.zero - var hitTestSlop:UIEdgeInsets = UIEdgeInsets.zero - override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { if hitTestSlop == UIEdgeInsets.zero { - return super.point(inside: point, with:event) + return super.point(inside: point, with: event) } - else{ + else { return self.bounds.inset(by: hitTestSlop).contains(point) } } - } diff --git a/View/BaseTableViewCell.swift b/View/BaseTableViewCell.swift index 03c1a4b..5dacced 100644 --- a/View/BaseTableViewCell.swift +++ b/View/BaseTableViewCell.swift @@ -9,8 +9,8 @@ import UIKit class BaseTableViewCell: UITableViewCell { - var viewModel:ViewModel? - func bindViewModel(model:ViewModel){ + var viewModel: ViewModel? + func bindViewModel(model: ViewModel) { self.viewModel = model } } diff --git a/View/GroupCellViewModel.swift b/View/GroupCellViewModel.swift index baaf04d..e912ba0 100644 --- a/View/GroupCellViewModel.swift +++ b/View/GroupCellViewModel.swift @@ -6,16 +6,16 @@ // Copyright © 2021 Fin. All rights reserved. // -import UIKit -import RxSwift import RxCocoa import RxDataSources +import RxSwift +import UIKit -class GroupCellViewModel:ViewModel { +class GroupCellViewModel: ViewModel { let name = BehaviorRelay(value: nil) let checked = BehaviorRelay(value: false) - init(groupFilterModel:GroupFilterModel) { + init(groupFilterModel: GroupFilterModel) { self.name.accept(groupFilterModel.name) self.checked.accept(groupFilterModel.checked) } diff --git a/View/GroupTableViewCell.swift b/View/GroupTableViewCell.swift index 735d938..f1b3120 100644 --- a/View/GroupTableViewCell.swift +++ b/View/GroupTableViewCell.swift @@ -6,16 +6,17 @@ // Copyright © 2021 Fin. All rights reserved. // -import UIKit import Material +import UIKit class GroupTableViewCell: BaseTableViewCell { - let nameLabel:UILabel = { + let nameLabel: UILabel = { let label = UILabel() label.fontSize = 14 label.textColor = Color.darkText.primary return label }() + let checkButton: BKButton = { let btn = BKButton() btn.setImage(UIImage(named: "baseline_radio_button_unchecked_black_24pt"), for: .normal) @@ -37,31 +38,33 @@ class GroupTableViewCell: BaseTableViewCell { make.left.equalToSuperview().offset(15) make.centerY.equalToSuperview() } - nameLabel.snp.makeConstraints { (make) in + nameLabel.snp.makeConstraints { make in make.left.equalTo(checkButton.snp.right).offset(15) make.top.equalToSuperview().offset(15) make.bottom.equalToSuperview().offset(-15) } let tap = UITapGestureRecognizer() self.contentView.addGestureRecognizer(tap) - tap.rx.event.subscribe(onNext: {[weak self] _ in + tap.rx.event.subscribe(onNext: { [weak self] _ in (self?.viewModel as? GroupCellViewModel)?.checked.accept(!self!.checkButton.isSelected) }).disposed(by: rx.disposeBag) } + + @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func bindViewModel(model:ViewModel){ + override func bindViewModel(model: ViewModel) { super.bindViewModel(model: model) guard let viewModel = model as? GroupCellViewModel else { return } viewModel.name - .map({ name in - return name ?? NSLocalizedString("default") - }) + .map { name in + name ?? NSLocalizedString("default") + } .bind(to: nameLabel.rx.text) .disposed(by: rx.reuseBag) @@ -70,10 +73,8 @@ class GroupTableViewCell: BaseTableViewCell { .disposed(by: rx.reuseBag) viewModel.checked.subscribe( - onNext: {[weak self] checked in + onNext: { [weak self] checked in self?.checkButton.tintColor = checked ? Color.lightBlue.darken3 : Color.lightGray }).disposed(by: rx.reuseBag) - - } } diff --git a/View/LabelCell.swift b/View/LabelCell.swift index ccb318e..b97620e 100644 --- a/View/LabelCell.swift +++ b/View/LabelCell.swift @@ -6,19 +6,21 @@ // Copyright © 2020 Fin. All rights reserved. // -import UIKit import Material +import UIKit class LabelCell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.selectionStyle = .none - + self.backgroundColor = Color.grey.lighten5 self.textLabel?.textColor = Color.darkText.secondary self.textLabel?.fontSize = 12 self.textLabel?.numberOfLines = 0 } + + @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/View/MessageTableViewCell.swift b/View/MessageTableViewCell.swift index 4c38447..d24f06a 100644 --- a/View/MessageTableViewCell.swift +++ b/View/MessageTableViewCell.swift @@ -6,10 +6,9 @@ // Copyright © 2020 Fin. All rights reserved. // -import UIKit import Material +import UIKit class MessageTableViewCell: BaseTableViewCell { - let backgroundPanel: UIView = { let view = UIView() view.layer.cornerRadius = 3 @@ -25,6 +24,7 @@ class MessageTableViewCell: BaseTableViewCell { label.numberOfLines = 0 return label }() + let bodyLabel: UILabel = { let label = UILabel() label.font = RobotoFont.regular(with: 14) @@ -49,12 +49,14 @@ class MessageTableViewCell: BaseTableViewCell { label.textColor = Color.darkText.others return label }() + let bodyStackView: UIStackView = { let stackView = UIStackView() stackView.axis = .vertical return stackView }() - let separatorLine:UIImageView = { + + let separatorLine: UIImageView = { let imageView = UIImageView() imageView.backgroundColor = Color.grey.lighten5 return imageView @@ -80,30 +82,32 @@ class MessageTableViewCell: BaseTableViewCell { layoutView() } + + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - func layoutView(){ - bodyStackView.snp.makeConstraints { (make) in + func layoutView() { + bodyStackView.snp.makeConstraints { make in make.left.top.equalToSuperview().offset(16) make.right.equalToSuperview().offset(-16) } - titleLabel.snp.remakeConstraints { (make) in + titleLabel.snp.remakeConstraints { make in make.left.equalTo(12) make.right.equalTo(-12) } - bodyLabel.snp.remakeConstraints { (make) in + bodyLabel.snp.remakeConstraints { make in make.left.right.equalTo(titleLabel) } - urlLabel.snp.makeConstraints { (make) in + urlLabel.snp.makeConstraints { make in make.left.right.equalTo(bodyLabel) } - dateLabel.snp.remakeConstraints { (make) in + dateLabel.snp.remakeConstraints { make in make.left.equalTo(bodyLabel) make.top.equalTo(bodyStackView.snp.bottom).offset(12) } - separatorLine.snp.remakeConstraints { (make) in + separatorLine.snp.remakeConstraints { make in make.left.right.bottom.equalToSuperview() make.top.equalTo(dateLabel.snp.bottom).offset(12) make.height.equalTo(10) @@ -127,12 +131,11 @@ class MessageTableViewCell: BaseTableViewCell { viewModel.url.bind(to: self.urlLabel.rx.text).disposed(by: rx.reuseBag) viewModel.date.bind(to: self.dateLabel.rx.text).disposed(by: rx.reuseBag) - viewModel.title.map{ $0.count <= 0}.bind(to: self.titleLabel.rx.isHidden).disposed(by: rx.reuseBag) - viewModel.url.map{ $0.count <= 0}.bind(to: self.urlLabel.rx.isHidden).disposed(by: rx.reuseBag) + viewModel.title.map { $0.count <= 0 }.bind(to: self.titleLabel.rx.isHidden).disposed(by: rx.reuseBag) + viewModel.url.map { $0.count <= 0 }.bind(to: self.urlLabel.rx.isHidden).disposed(by: rx.reuseBag) self.urlLabel.gestureRecognizers?.first?.rx.event - .map{[weak self] _ in self?.urlLabel.text ?? "" } + .map { [weak self] _ in self?.urlLabel.text ?? "" } .bind(to: viewModel.urlTap).disposed(by: rx.reuseBag) - } } diff --git a/View/MessageTableViewCellViewModel.swift b/View/MessageTableViewCellViewModel.swift index a46bfb1..d4452ea 100644 --- a/View/MessageTableViewCellViewModel.swift +++ b/View/MessageTableViewCellViewModel.swift @@ -6,12 +6,11 @@ // Copyright © 2020 Fin. All rights reserved. // +import Differentiator import Foundation import RxCocoa -import Differentiator import RxDataSources - class MessageTableViewCellViewModel: ViewModel { let message: Message @@ -22,7 +21,7 @@ class MessageTableViewCellViewModel: ViewModel { let urlTap: PublishRelay - init(message:Message) { + init(message: Message) { self.message = message self.title = BehaviorRelay(value: message.title ?? "") @@ -35,13 +34,12 @@ class MessageTableViewCellViewModel: ViewModel { } } - struct MessageSection { var header: String - var messages:[MessageTableViewCellViewModel] + var messages: [MessageTableViewCellViewModel] } -extension MessageSection:AnimatableSectionModelType { +extension MessageSection: AnimatableSectionModelType { typealias Item = MessageTableViewCellViewModel typealias Identity = String @@ -62,7 +60,7 @@ extension MessageSection:AnimatableSectionModelType { extension MessageTableViewCellViewModel: IdentifiableType { typealias Identity = String - var identity: String{ + var identity: String { return "\(self.message.id)" } diff --git a/View/PreviewCardCell.swift b/View/PreviewCardCell.swift index 8d32f05..7b8def4 100644 --- a/View/PreviewCardCell.swift +++ b/View/PreviewCardCell.swift @@ -6,11 +6,10 @@ // Copyright © 2018 Fin. All rights reserved. // -import UIKit import Material +import UIKit class PreviewCardCell: BaseTableViewCell { - let previewButton = IconButton(image: Icon.cm.skipForward, tintColor: Color.grey.base) let copyButton = IconButton(image: UIImage(named: "baseline_file_copy_white_24pt"), tintColor: Color.grey.base) @@ -21,6 +20,7 @@ class PreviewCardCell: BaseTableViewCell { label.numberOfLines = 0 return label }() + let bodyLabel: UILabel = { let label = UILabel() label.font = RobotoFont.regular(with: 14) @@ -37,12 +37,14 @@ class PreviewCardCell: BaseTableViewCell { label.isUserInteractionEnabled = true return label }() - let contentImageView:UIImageView = { + + let contentImageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFit return imageView }() - let card:UIView = { + + let card: UIView = { let view = UIView() view.backgroundColor = Color.white view.layer.cornerRadius = 2 @@ -60,6 +62,7 @@ class PreviewCardCell: BaseTableViewCell { return label }() + @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -73,23 +76,22 @@ class PreviewCardCell: BaseTableViewCell { card.addSubview(copyButton) card.addSubview(previewButton) - card.snp.makeConstraints { (make) in + card.snp.makeConstraints { make in make.left.top.equalToSuperview().offset(16) make.right.equalToSuperview().offset(-16) make.bottom.equalToSuperview() } - previewButton.snp.makeConstraints { (make) in + previewButton.snp.makeConstraints { make in make.right.equalToSuperview().offset(-10) make.centerY.equalTo(card.snp.top).offset(40) make.width.height.equalTo(40) } - copyButton.snp.makeConstraints { (make) in + copyButton.snp.makeConstraints { make in make.right.equalTo(previewButton.snp.left).offset(-10) make.centerY.equalTo(previewButton) make.width.height.equalTo(40) } - let titleStackView = UIStackView() titleStackView.axis = .vertical titleStackView.addArrangedSubview(titleLabel) @@ -97,20 +99,19 @@ class PreviewCardCell: BaseTableViewCell { card.addSubview(titleStackView) - titleLabel.snp.makeConstraints { (make) in + titleLabel.snp.makeConstraints { make in make.left.equalToSuperview().offset(15) } - bodyLabel.snp.makeConstraints { (make) in + bodyLabel.snp.makeConstraints { make in make.left.equalToSuperview().offset(15) } - titleStackView.snp.makeConstraints { (make) in + titleStackView.snp.makeConstraints { make in make.centerY.equalTo(copyButton) make.left.equalToSuperview() make.right.equalTo(copyButton.snp.left) } - let contentStackView = UIStackView() contentStackView.axis = .vertical contentStackView.spacing = 20 @@ -121,18 +122,18 @@ class PreviewCardCell: BaseTableViewCell { contentStackView.addArrangedSubview(contentLabel) contentStackView.addArrangedSubview(noticeLabel) - contentLabel.snp.makeConstraints { (make) in + contentLabel.snp.makeConstraints { make in make.left.equalToSuperview().offset(12) make.right.equalToSuperview().offset(-12) } - contentImageView.snp.remakeConstraints { (make) in + contentImageView.snp.remakeConstraints { make in make.left.right.equalToSuperview() } - noticeLabel.snp.makeConstraints { (make) in + noticeLabel.snp.makeConstraints { make in make.left.equalTo(10) make.right.equalTo(-10) } - contentStackView.snp.makeConstraints { (make) in + contentStackView.snp.makeConstraints { make in make.left.right.equalToSuperview() make.top.equalTo(previewButton.snp.bottom).offset(20) make.bottom.equalToSuperview().offset(-10) @@ -141,7 +142,6 @@ class PreviewCardCell: BaseTableViewCell { noticeLabel.addGestureRecognizer(UITapGestureRecognizer()) } - override func bindViewModel(model: ViewModel) { guard let viewModel = model as? PreviewCardCellViewModel else { return @@ -155,7 +155,7 @@ class PreviewCardCell: BaseTableViewCell { viewModel.notice .bind(to: self.noticeLabel.rx.attributedText).disposed(by: rx.reuseBag) viewModel.contentImage - .compactMap{ $0 } + .compactMap { $0 } .bind(to: self.contentImageView.rx.image) .disposed(by: rx.reuseBag) viewModel.contentImage @@ -163,34 +163,33 @@ class PreviewCardCell: BaseTableViewCell { .bind(to: self.contentImageView.rx.isHidden) .disposed(by: rx.reuseBag) - //点击通知 + // 点击通知 noticeLabel.gestureRecognizers!.first! .rx.event - .compactMap{[weak weakModel = viewModel](_) -> ViewModel? in - //仅在有 moreViewModel 时 点击 - return weakModel?.previewModel.moreViewModel + .compactMap { [weak weakModel = viewModel] _ -> ViewModel? in + // 仅在有 moreViewModel 时 点击 + weakModel?.previewModel.moreViewModel } .bind(to: viewModel.noticeTap) .disposed(by: rx.reuseBag) - //点击复制 - copyButton.rx.tap.map {[weak self] () -> String in - return self?.contentLabel.text ?? "" + // 点击复制 + copyButton.rx.tap.map { [weak self] () -> String in + self?.contentLabel.text ?? "" } .bind(to: viewModel.copy) .disposed(by: rx.reuseBag) - //点击预览 - previewButton.rx.tap.compactMap {[weak self] () -> URL? in + // 点击预览 + previewButton.rx.tap.compactMap { [weak self] () -> URL? in if let urlStr = self?.contentLabel.text?.urlEncoded(), - let url = URL(string: urlStr){ + let url = URL(string: urlStr) + { return url } return nil } .bind(to: viewModel.preview) .disposed(by: rx.reuseBag) - } - } diff --git a/View/PreviewCardCellViewModel.swift b/View/PreviewCardCellViewModel.swift index c586172..2080351 100644 --- a/View/PreviewCardCellViewModel.swift +++ b/View/PreviewCardCellViewModel.swift @@ -20,8 +20,8 @@ class PreviewCardCellViewModel: ViewModel { let copy = PublishRelay() let preview = PublishRelay() - let previewModel:PreviewModel - init( previewModel:PreviewModel, clientState: Driver ) { + let previewModel: PreviewModel + init(previewModel: PreviewModel, clientState: Driver) { self.previewModel = previewModel contentImage = BehaviorRelay(value: previewModel.image) @@ -39,30 +39,29 @@ class PreviewCardCellViewModel: ViewModel { // 因为这时可能 ServerManager.shared.currentAddress 或 Client.shared.key 发生了改变。 // 这不是一个好的写法,viewModel 应尽可能只依赖固定的 input ,而不应依赖不可预测的外部变量( currentAddress 与 key )。 // 但这个项目是由 MVC 临时重构为 MVVM ,之前是这样写的,所以懒得改动了。 - clientState.compactMap({[weak self] (_) -> NSAttributedString? in - return self?.contentAttrStr() - }) + clientState.compactMap { [weak self] _ -> NSAttributedString? in + self?.contentAttrStr() + } .drive(content) .disposed(by: rx.disposeBag) - let noticeStr = "\(previewModel.notice ?? "")" let noticeAttrStr = NSMutableAttributedString(string: noticeStr, attributes: [ NSAttributedString.Key.foregroundColor: Color.grey.base, - NSAttributedString.Key.font : RobotoFont.regular(with: 12) + NSAttributedString.Key.font: RobotoFont.regular(with: 12) ]) if let moreInfo = previewModel.moreInfo { noticeAttrStr.append(NSMutableAttributedString(string: " \(moreInfo)", attributes: [ NSAttributedString.Key.foregroundColor: Color.blue.base, - NSAttributedString.Key.font : RobotoFont.regular(with: 12) + NSAttributedString.Key.font: RobotoFont.regular(with: 12) ])) } notice.accept(noticeAttrStr) } func contentAttrStr() -> NSAttributedString { - var fontSize:CGFloat = 14 + var fontSize: CGFloat = 14 if UIScreen.main.bounds.size.width <= 320 { fontSize = 11 } @@ -70,31 +69,31 @@ class PreviewCardCellViewModel: ViewModel { let attrStr = NSMutableAttributedString(string: "") attrStr.append(NSAttributedString(string: serverUrl.absoluteString, attributes: [ NSAttributedString.Key.foregroundColor: Color.grey.darken4, - NSAttributedString.Key.font : RobotoFont.regular(with: fontSize) - ])) + NSAttributedString.Key.font: RobotoFont.regular(with: fontSize) + ])) attrStr.append(NSAttributedString(string: "/\(Client.shared.key ?? "Your Key")", attributes: [ NSAttributedString.Key.foregroundColor: Color.grey.darken3, - NSAttributedString.Key.font : RobotoFont.regular(with: fontSize) - ])) + NSAttributedString.Key.font: RobotoFont.regular(with: fontSize) + ])) if let modelTitle = previewModel.title { attrStr.append(NSAttributedString(string: "/\(modelTitle)", attributes: [ NSAttributedString.Key.foregroundColor: Color.grey.darken1, - NSAttributedString.Key.font : RobotoFont.regular(with: fontSize) - ])) + NSAttributedString.Key.font: RobotoFont.regular(with: fontSize) + ])) } if let modelBody = previewModel.body { attrStr.append(NSAttributedString(string: "/\(modelBody)", attributes: [ NSAttributedString.Key.foregroundColor: Color.grey.base, - NSAttributedString.Key.font : RobotoFont.regular(with: fontSize) - ])) + NSAttributedString.Key.font: RobotoFont.regular(with: fontSize) + ])) } if let queryParameter = previewModel.queryParameter { attrStr.append(NSAttributedString(string: "?\(queryParameter)", attributes: [ NSAttributedString.Key.foregroundColor: Color.grey.lighten1, - NSAttributedString.Key.font : RobotoFont.regular(with: fontSize) - ])) + NSAttributedString.Key.font: RobotoFont.regular(with: fontSize) + ])) } return attrStr diff --git a/View/SoundCell.swift b/View/SoundCell.swift index 3087cef..320107f 100644 --- a/View/SoundCell.swift +++ b/View/SoundCell.swift @@ -6,24 +6,26 @@ // Copyright © 2020 Fin. All rights reserved. // -import UIKit -import Material import AVKit +import Material +import UIKit class SoundCell: BaseTableViewCell { let copyButton = IconButton(image: UIImage(named: "baseline_file_copy_white_24pt"), tintColor: Color.grey.base) - let nameLabel:UILabel = { + let nameLabel: UILabel = { let label = UILabel() label.fontSize = 14 label.textColor = Color.darkText.primary return label }() - let durationLabel:UILabel = { + + let durationLabel: UILabel = { let label = UILabel() label.fontSize = 12 label.textColor = Color.darkText.secondary return label }() + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.selectionStyle = .none @@ -32,25 +34,27 @@ class SoundCell: BaseTableViewCell { self.contentView.addSubview(durationLabel) self.contentView.addSubview(copyButton) - nameLabel.snp.makeConstraints { (make) in + nameLabel.snp.makeConstraints { make in make.left.top.equalToSuperview().offset(15) } - durationLabel.snp.makeConstraints { (make) in + durationLabel.snp.makeConstraints { make in make.left.equalTo(nameLabel) make.top.equalTo(nameLabel.snp.bottom).offset(5) make.bottom.equalToSuperview().offset(-15) } - copyButton.snp.makeConstraints { (make) in + copyButton.snp.makeConstraints { make in make.right.equalToSuperview().offset(-15) make.centerY.equalToSuperview() make.width.height.equalTo(40) } } + + @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func bindViewModel(model:ViewModel){ + override func bindViewModel(model: ViewModel) { super.bindViewModel(model: model) guard let viewModel = model as? SoundCellViewModel else { return @@ -60,12 +64,12 @@ class SoundCell: BaseTableViewCell { .bind(to: nameLabel.rx.text) .disposed(by: rx.reuseBag) viewModel.duration - .map { String(format: "%.2g second(s)", CMTimeGetSeconds($0) ) } + .map { String(format: "%.2g second(s)", CMTimeGetSeconds($0)) } .bind(to: durationLabel.rx.text) .disposed(by: rx.reuseBag) copyButton.rx.tap - .map{ viewModel.name.value } + .map { viewModel.name.value } .bind(to: viewModel.copyNameAction) .disposed(by: rx.reuseBag) } diff --git a/View/SoundCellViewModel.swift b/View/SoundCellViewModel.swift index 0f39210..91febd5 100644 --- a/View/SoundCellViewModel.swift +++ b/View/SoundCellViewModel.swift @@ -6,18 +6,18 @@ // Copyright © 2020 Fin. All rights reserved. // -import Foundation -import RxSwift -import RxCocoa import AVKit +import Foundation +import RxCocoa +import RxSwift -class SoundCellViewModel:ViewModel { +class SoundCellViewModel: ViewModel { let name = BehaviorRelay(value: "") let duration = BehaviorRelay(value: .zero) - + let copyNameAction = PublishRelay() let playAction = PublishRelay() - + let model: AVURLAsset init(model: AVURLAsset) { self.model = model diff --git a/View/SpacerCell.swift b/View/SpacerCell.swift index ce3957f..c7edafe 100644 --- a/View/SpacerCell.swift +++ b/View/SpacerCell.swift @@ -9,18 +9,21 @@ import UIKit class SpacerCell: UITableViewCell { - var height:CGFloat = 0 { - didSet{ + var height: CGFloat = 0 { + didSet { self.contentView.snp.remakeConstraints { make in make.height.equalTo(height) } } } + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.backgroundColor = UIColor.clear self.selectionStyle = .none } + + @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/View/TextCell.swift b/View/TextCell.swift index acb7ca5..a2f69aa 100644 --- a/View/TextCell.swift +++ b/View/TextCell.swift @@ -9,14 +9,14 @@ import UIKit class DetailTextCell: UITableViewCell { - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: .value1, reuseIdentifier: reuseIdentifier) self.selectionStyle = .none self.accessoryType = .disclosureIndicator } + + @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - } diff --git a/View/UINavigationItem+Extension.swift b/View/UINavigationItem+Extension.swift index 9c101d5..99998e9 100644 --- a/View/UINavigationItem+Extension.swift +++ b/View/UINavigationItem+Extension.swift @@ -8,40 +8,40 @@ import UIKit -//如果第一个 item 是系统自带的 UIBarButtonItem ,则距离导航栏左右距离只有8 -//自己定义的的则最少有16,太宽了 -//所以先用 一个 fixedSpace UIBarButtonItem 先把距离给缩短点, -//然后用个 AlignmentRectInsetsOverridable 把自己的按钮往 左/右 挪动,减少距离 -//用 HitTestSlopable 增加点击区域 +// 如果第一个 item 是系统自带的 UIBarButtonItem ,则距离导航栏左右距离只有8 +// 自己定义的的则最少有16,太宽了 +// 所以先用 一个 fixedSpace UIBarButtonItem 先把距离给缩短点, +// 然后用个 AlignmentRectInsetsOverridable 把自己的按钮往 左/右 挪动,减少距离 +// 用 HitTestSlopable 增加点击区域 extension UINavigationItem { - - func setLeftBarButtonItem(item: UIBarButtonItem){ + func setLeftBarButtonItem(item: UIBarButtonItem) { setBarButtonItems(items: [item], left: true) } - func setRightBarButtonItem(item: UIBarButtonItem){ + + func setRightBarButtonItem(item: UIBarButtonItem) { setBarButtonItems(items: [item], left: false) } - - func setBarButtonItems(items: [UIBarButtonItem], left:Bool){ + + func setBarButtonItems(items: [UIBarButtonItem], left: Bool) { guard items.count > 0 else { self.leftBarButtonItems = nil return } var buttonItems = items if #available(iOS 11.0, *) { - buttonItems.forEach { (item) in - guard let view = item.customView else {return} + buttonItems.forEach { item in + guard let view = item.customView else { return } item.customView?.translatesAutoresizingMaskIntoConstraints = false (item.customView as? HitTestSlopable)?.hitTestSlop = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10) (item.customView as? AlignmentRectInsetsOverridable)?.alignmentRectInsetsOverride = UIEdgeInsets(top: 0, left: left ? 8 : -8, bottom: 0, right: left ? -8 : 8) - item.customView?.snp.makeConstraints({ (make) in + item.customView?.snp.makeConstraints { make in make.width.equalTo(view.bounds.size.width > 24 ? view.bounds.width : 24) make.height.equalTo(view.bounds.size.height > 24 ? view.bounds.height : 24) - }) + } } buttonItems.insert(UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil), at: 0) } - else{ + else { let spacer = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) spacer.width = -8 buttonItems.insert(spacer, at: 0) diff --git a/View/iCloudStatusCell.swift b/View/iCloudStatusCell.swift index ef3fcec..dba620e 100644 --- a/View/iCloudStatusCell.swift +++ b/View/iCloudStatusCell.swift @@ -6,22 +6,22 @@ // Copyright © 2020 Fin. All rights reserved. // -import UIKit import CloudKit +import UIKit class iCloudStatusCell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: .value1, reuseIdentifier: reuseIdentifier) self.selectionStyle = .none - + self.textLabel?.text = NSLocalizedString("iCloudSatatus") self.detailTextLabel?.text = "" - CKContainer.default().accountStatus { (status, error) in + CKContainer.default().accountStatus { status, _ in dispatch_sync_safely_main_queue { switch status { case .available: self.detailTextLabel?.text = NSLocalizedString("available") - + case .noAccount, .restricted: self.detailTextLabel?.text = NSLocalizedString("restricted") case .couldNotDetermine: @@ -32,6 +32,8 @@ class iCloudStatusCell: UITableViewCell { } } } + + @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/notificationContentExtension/NotificationViewController.swift b/notificationContentExtension/NotificationViewController.swift index 19f69df..5714f65 100644 --- a/notificationContentExtension/NotificationViewController.swift +++ b/notificationContentExtension/NotificationViewController.swift @@ -11,59 +11,58 @@ import UserNotifications import UserNotificationsUI class NotificationViewController: UIViewController, UNNotificationContentExtension { - - let noticeLabel:UILabel = { + let noticeLabel: UILabel = { let label = UILabel() - label.textColor = UIColor.init(named: "notification_copy_color") + label.textColor = UIColor(named: "notification_copy_color") label.text = NSLocalizedString("Copy", comment: "") label.font = UIFont.systemFont(ofSize: 16) label.textAlignment = .center return label }() - + override func viewDidLoad() { super.viewDidLoad() self.view.addSubview(self.noticeLabel) self.preferredContentSize = CGSize(width: 0, height: 1) } + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.preferredContentSize = CGSize(width: 0, height: 1) } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.preferredContentSize = CGSize(width: 0, height: 1) } - + func didReceive(_ notification: UNNotification) { guard notification.request.content.userInfo["autocopy"] as? String == "1" - || notification.request.content.userInfo["automaticallycopy"] as? String == "1" else { + || notification.request.content.userInfo["automaticallycopy"] as? String == "1" + else { return } - if let copy = notification.request.content.userInfo["copy"] as? String - { + if let copy = notification.request.content.userInfo["copy"] as? String { UIPasteboard.general.string = copy } - else{ + else { UIPasteboard.general.string = notification.request.content.body } } - + func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) { - let userInfo = response.notification.request.content.userInfo - + if let copy = userInfo["copy"] as? String { UIPasteboard.general.string = copy } - else{ + else { UIPasteboard.general.string = response.notification.request.content.body } self.preferredContentSize = CGSize(width: 0, height: 40) self.noticeLabel.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 40) - - completion(.doNotDismiss) + completion(.doNotDismiss) } }