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