格式化代码

This commit is contained in:
Fin 2021-10-12 16:02:34 +08:00
parent 7be222b370
commit 13a11ebcf4
55 changed files with 827 additions and 857 deletions

View File

@ -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 havent migrated anything yet, so oldSchemaVersion == 0 // We havent 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:.
} }
} }

View File

@ -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.
} }
} }
} }

View File

@ -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()
} }
} }

View File

@ -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]
} }

View File

@ -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("没有打开推送")
} }
}) })

View File

@ -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
} }

View File

@ -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 0iOS12 便 // iOS 11 safeAreaInsets.top 0iOS12 便
} }
return UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0) return UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
}() }()

View File

@ -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
} }
} }
} }
} }

View File

@ -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"
} }
} }
} }

View File

@ -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) {

View File

@ -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

View File

@ -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()
}) })

View File

@ -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
} }
} }

View File

@ -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")
} }

View File

@ -9,14 +9,14 @@
import UIKit import UIKit
extension String { extension String {
//urlurl // urlurl
func urlEncoded() -> String { func urlEncoded() -> String {
let encodeUrlString = self.addingPercentEncoding(withAllowedCharacters: let encodeUrlString = self.addingPercentEncoding(withAllowedCharacters:
.urlQueryAllowed) .urlQueryAllowed)
return encodeUrlString ?? "" return encodeUrlString ?? ""
} }
//urlurl // urlurl
func urlDecoded() -> String { func urlDecoded() -> String {
return self.removingPercentEncoding ?? "" return self.removingPercentEncoding ?? ""
} }

View File

@ -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! //contextget~ return image! // contextget~
} }
var image: UIImage { var image: UIImage {
return UIColor.image(color: self) return UIColor.image(color: self)
} }

View File

@ -16,4 +16,4 @@ protocol ViewModelType {
func transform(input: Input) -> Output func transform(input: Input) -> Output
} }
class ViewModel:NSObject{ } class ViewModel: NSObject {}

View File

@ -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)
} }
} }
} }

View File

@ -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
}
} }

View File

@ -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(){
}
} }

View File

@ -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 {}
}

View File

@ -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())
) )
} }
} }

View File

@ -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)
} }
} }
} }

View File

@ -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)
//previewnotice // previewnotice
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
) )
} }
} }

View File

@ -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)
// APP1
//APP1
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)
//messagealert // messagealert
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)
//messageURL // messageURL
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

View File

@ -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)

View File

@ -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)
} }
} }

View File

@ -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?)
} }

View File

@ -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)
} }
} }

View File

@ -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:

View File

@ -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)

View File

@ -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 }
) )
} }
} }

View File

@ -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)
} }

View File

@ -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"]
} }
} }

View File

@ -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

View File

@ -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 havent migrated anything yet, so oldSchemaVersion == 0 // We havent 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)

View File

@ -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)
} }
} }

View File

@ -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()
} }

View File

@ -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

View File

@ -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)
} }
} }
} }

View File

@ -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
} }
} }

View File

@ -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)
} }

View File

@ -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)
} }
} }

View File

@ -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")
} }

View File

@ -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)
} }
} }

View File

@ -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)"
} }

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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)
} }

View File

@ -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

View File

@ -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")
} }

View File

@ -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")
} }
} }

View File

@ -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)

View File

@ -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")
} }

View File

@ -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)
} }
} }