格式化代码

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.
//
import UIKit
import Material
import UserNotifications
import RealmSwift
import IceCream
import Material
import RealmSwift
import UIKit
import UserNotifications
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate{
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
var window: UIWindow?
var syncEngine: SyncEngine?
func setupRealm() {
@ -23,18 +22,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
let config = Realm.Configuration(
fileURL: fileUrl,
schemaVersion: 13,
migrationBlock: { migration, oldSchemaVersion in
migrationBlock: { _, oldSchemaVersion in
// We havent migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
if oldSchemaVersion < 1 {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
})
}
)
// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config
//iCloud
// iCloud
syncEngine = SyncEngine(objects: [
SyncObject(type: Message.self)
], databaseScope: .private)
@ -44,54 +44,54 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
print("message count: \(realm?.objects(Message.self).count ?? 0)")
#endif
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Realm()
// Realm()
setupRealm()
self.window = UIWindow(frame: UIScreen.main.bounds)
if #available(iOS 13.0, *) {
self.window?.overrideUserInterfaceStyle = .light
}
let tabBarController = StateStorageTabBarController()
tabBarController.tabBar.tintColor = UIColor.black
self.window?.backgroundColor = UIColor.black
self.window?.rootViewController = BarkSnackbarController(
rootViewController: tabBarController
)
tabBarController.viewControllers = [
BarkNavigationController(rootViewController:HomeViewController(viewModel: HomeViewModel())),
BarkNavigationController(rootViewController:MessageListViewController(viewModel: MessageListViewModel())),
BarkNavigationController(rootViewController:MessageSettingsViewController(viewModel: MessageSettingsViewModel())),
BarkNavigationController(rootViewController: HomeViewController(viewModel: HomeViewModel())),
BarkNavigationController(rootViewController: MessageListViewController(viewModel: MessageListViewModel())),
BarkNavigationController(rootViewController: MessageSettingsViewController(viewModel: MessageSettingsViewModel()))
]
let tabBarItems = [UITabBarItem(title: NSLocalizedString("service"), image: UIImage(named: "baseline_gite_black_24pt"), tag: 0),
UITabBarItem(title: NSLocalizedString("historyMessage"), image: Icon.history, tag: 1),
UITabBarItem(title: NSLocalizedString("settings"), image: UIImage(named: "baseline_manage_accounts_black_24pt"), tag: 2)]
for (index , viewController) in tabBarController.viewControllers!.enumerated() {
for (index, viewController) in tabBarController.viewControllers!.enumerated() {
viewController.tabBarItem = tabBarItems[index]
}
self.window?.makeKeyAndVisible()
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().setNotificationCategories([
UNNotificationCategory(identifier: "myNotificationCategory", actions: [
UNNotificationAction(identifier: "copy", title: NSLocalizedString("Copy2"), options: UNNotificationActionOptions.foreground)
], intentIdentifiers: [], options: .customDismissAction)
])
], intentIdentifiers: [], options: .customDismissAction)
])
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
UNUserNotificationCenter.current().getNotificationSettings { settings in
dispatch_sync_safely_main_queue {
if settings.authorizationStatus == .authorized {
Client.shared.registerForRemoteNotifications()
}
}
}
//
//
let bar = UINavigationBar.appearance(whenContainedInInstancesOf: [BarkNavigationController.self])
bar.backIndicatorImage = UIImage(named: "back")
bar.backIndicatorTransitionMaskImage = UIImage(named: "back")
@ -103,56 +103,57 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print(error)
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let deviceTokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
Settings[.deviceToken] = deviceTokenString
//
//
Client.shared.bindDeviceToken()
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
notificatonHandler(userInfo: notification.request.content.userInfo)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
notificatonHandler(userInfo: response.notification.request.content.userInfo)
}
private func notificatonHandler(userInfo:[AnyHashable:Any]){
private func notificatonHandler(userInfo: [AnyHashable: Any]) {
let navigationController = Client.shared.currentNavigationController
func presentController(){
let alert = (userInfo["aps"] as? [String:Any])?["alert"] as? [String:Any]
func presentController() {
let alert = (userInfo["aps"] as? [String: Any])?["alert"] as? [String: Any]
let title = alert?["title"] as? String
let body = alert?["body"] as? String
let url:URL? = {
let url: URL? = {
if let url = userInfo["url"] as? String {
return URL(string: url)
}
return nil
}()
//URL
// URL
if let url = url {
if ["http","https"].contains(url.scheme?.lowercased() ?? ""){
if ["http", "https"].contains(url.scheme?.lowercased() ?? "") {
navigationController?.present(BarkSFSafariViewController(url: url), animated: true, completion: nil)
}
else{
else {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
return
}
let alertController = UIAlertController(title: title, message: body, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "复制内容", style: .default, handler: { (_) in
alertController.addAction(UIAlertAction(title: "复制内容", style: .default, handler: { _ in
if let copy = userInfo["copy"] as? String {
UIPasteboard.general.string = copy
}
else{
else {
UIPasteboard.general.string = body
}
}))
alertController.addAction(UIAlertAction(title: "更多操作", style: .default, handler: { (_) in
alertController.addAction(UIAlertAction(title: "更多操作", style: .default, handler: { _ in
var shareContent = ""
if let title = title {
shareContent += "\(title)\n"
@ -160,15 +161,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
if let body = body {
shareContent += "\(body)\n"
}
for (key,value) in userInfo {
if ["aps","title","body","url"].contains((key as? String) ?? "") {
for (key, value) in userInfo {
if ["aps", "title", "body", "url"].contains((key as? String) ?? "") {
continue
}
shareContent += "\(key): \(value) \n"
}
var items:[Any] = []
var items: [Any] = []
items.append(shareContent)
if let url = url{
if let url = url {
items.append(url)
}
let controller = UIApplication.shared.keyWindow?.rootViewController
@ -177,16 +178,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
controller?.present(activityController, animated: true, completion: nil)
}))
alertController.addAction(UIAlertAction(title: "取消", style: .cancel, handler: nil))
navigationController?.present(alertController, animated: true, completion: nil)
}
if let presentedController = navigationController?.presentedViewController {
presentedController.dismiss(animated: false) {
presentController()
}
}
else{
else {
presentController()
}
}
@ -202,7 +203,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
func applicationWillEnterForeground(_ application: UIApplication) {
if (Client.shared.key?.count ?? 0) <= 0{
if (Client.shared.key?.count ?? 0) <= 0 {
Client.shared.bindDeviceToken()
}
}
@ -214,7 +215,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}

View File

@ -9,7 +9,6 @@
import XCTest
class BarkTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
@ -29,5 +28,4 @@ class BarkTests: XCTestCase {
// Put the code you want to measure the time of here.
}
}
}

View File

@ -10,17 +10,17 @@ import UIKit
class ArchiveSettingManager: NSObject {
static let shared = ArchiveSettingManager()
let defaults = UserDefaults.init(suiteName: "group.bark")
let defaults = UserDefaults(suiteName: "group.bark")
var isArchive: Bool {
get {
return defaults?.value(forKey: "isArchive") as? Bool ?? true
return defaults?.value(forKey: "isArchive") as? Bool ?? true
}
set{
set {
defaults?.set(newValue, forKey: "isArchive")
}
}
private override init(){
override private init() {
super.init()
}
}

View File

@ -6,10 +6,10 @@
// Copyright © 2018 Fin. All rights reserved.
//
import UIKit
import DefaultsKit
import UIKit
enum BarkSettingKey:String {
enum BarkSettingKey: String {
/// key
case key = "me.fin.bark.key"
case servers = "me.fin.bark.servers"
@ -20,13 +20,11 @@ enum BarkSettingKey:String {
case selectedViewControllerIndex = "me.fin.bark.selectedViewControllerIndex"
}
class BarkSettings{
class BarkSettings {
static let shared = BarkSettings()
private init(){
}
private init() {}
subscript(key:String) -> String? {
subscript(key: String) -> String? {
get {
let storeKey = Key<String>(key)
return Defaults.shared.get(for: storeKey)
@ -42,7 +40,7 @@ class BarkSettings{
}
}
subscript(key:BarkSettingKey) -> String? {
subscript(key: BarkSettingKey) -> String? {
get {
return self[key.rawValue]
}
@ -51,7 +49,7 @@ class BarkSettings{
}
}
subscript<T : Codable>(key:String) -> T? {
subscript<T: Codable>(key: String) -> T? {
get {
let storeKey = Key<T>(key)
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 {
return self[key.rawValue]
}

View File

@ -6,31 +6,29 @@
// Copyright © 2018 Fin. All rights reserved.
//
import RxCocoa
import RxSwift
import UIKit
import UserNotifications
import RxSwift
import RxCocoa
class Client: NSObject {
static let shared = Client()
private override init() {
override private init() {
super.init()
}
var currentNavigationController:UINavigationController? {
get {
let controller = UIApplication.shared.delegate?.window??.rootViewController as? BarkSnackbarController
let nav = (controller?.rootViewController as? UITabBarController)?.selectedViewController as? UINavigationController
return nav
}
var currentNavigationController: UINavigationController? {
let controller = UIApplication.shared.delegate?.window??.rootViewController as? BarkSnackbarController
let nav = (controller?.rootViewController as? UITabBarController)?.selectedViewController as? UINavigationController
return nav
}
var currentTabBarController:StateStorageTabBarController? {
get {
let controller = UIApplication.shared.delegate?.window??.rootViewController as? BarkSnackbarController
return controller?.rootViewController as? StateStorageTabBarController
}
var currentTabBarController: StateStorageTabBarController? {
let controller = UIApplication.shared.delegate?.window??.rootViewController as? BarkSnackbarController
return controller?.rootViewController as? StateStorageTabBarController
}
let appVersion:String = {
let appVersion: String = {
var version = "0.0.0"
if let infoDict = Bundle.main.infoDictionary {
if let appVersion = infoDict["CFBundleVersion"] as? String {
@ -40,10 +38,10 @@ class Client: NSObject {
return version
}()
private var _key:String?
var key:String? {
private var _key: String?
var key: String? {
get {
if _key == nil, let aKey = Settings[.key]{
if _key == nil, let aKey = Settings[.key] {
_key = aKey
}
return _key
@ -62,24 +60,24 @@ class Client: NSObject {
var state = BehaviorRelay<ClienState>(value: .ok)
var dispose:Disposable?
func bindDeviceToken(){
if let token = Settings[.deviceToken] , token.count > 0{
var dispose: Disposable?
func bindDeviceToken() {
if let token = Settings[.deviceToken], token.count > 0 {
dispose?.dispose()
dispose = BarkApi.provider
.request(.register(
key: key,
devicetoken: token))
key: key,
devicetoken: token))
.filterResponseError()
.map { (json) -> ClienState in
.map { json -> ClienState in
switch json {
case .success(let json):
if let key = json["data","key"].rawString() {
if let key = json["data", "key"].rawString() {
Client.shared.key = key
return .ok
}
else{
else {
return .serverError
}
case .failure:
@ -92,13 +90,13 @@ class Client: NSObject {
func registerForRemoteNotifications() {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert , .sound , .badge], completionHandler: {(_ granted: Bool, _ error: Error?) -> Void in
center.requestAuthorization(options: [.alert, .sound, .badge], completionHandler: { (_ granted: Bool, _: Error?) -> Void in
if granted {
dispatch_sync_safely_main_queue {
UIApplication.shared.registerForRemoteNotifications()
}
}
else{
else {
print("没有打开推送")
}
})

View File

@ -9,23 +9,22 @@
import UIKit
extension Date {
func formatString(format:String) -> String {
func formatString(format: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = format
return formatter.string(for: self) ?? ""
}
func agoFormatString() -> String {
let clendar = NSCalendar(calendarIdentifier: .gregorian)
let cps = clendar?.components([ .hour, .minute, .second, .day, .month, .year], from: self, to: Date(), options: .wrapComponents)
let cps = clendar?.components([.hour, .minute, .second, .day, .month, .year], from: self, to: Date(), options: .wrapComponents)
let year = cps!.year!
let month = cps!.month!
let day = cps!.day!
let hour = cps!.hour!
let minute = cps!.minute!
if year > 0 || month > 0 || day > 0 || hour > 12 {
return formatString(format: "yyyy-MM-dd HH:mm")
}
@ -44,20 +43,24 @@ extension Date {
extension Date {
static var yesterday: Date { return Date().dayBefore }
static var tomorrow: Date { return Date().dayAfter }
static var lastHour: Date { return Calendar.current.date(byAdding: .hour, value: -1, to: Date())! }
static var tomorrow: Date { return Date().dayAfter }
static var lastHour: Date { return Calendar.current.date(byAdding: .hour, value: -1, to: Date())! }
var dayBefore: Date {
return Calendar.current.date(byAdding: .day, value: -1, to: noon)!
}
var dayAfter: Date {
return Calendar.current.date(byAdding: .day, value: 1, to: noon)!
}
var noon: Date {
return Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: self)!
}
var month: Int {
return Calendar.current.component(.month, from: self)
return Calendar.current.component(.month, from: self)
}
var isLastDayOfMonth: Bool {
return dayAfter.month != month
}

View File

@ -1,4 +1,4 @@
//
//
// Defines.swift
// Bark
//
@ -9,7 +9,7 @@
import UIKit
/// 线
func dispatch_sync_safely_main_queue(_ block: ()->()) {
func dispatch_sync_safely_main_queue(_ block: () -> ()) {
if Thread.isMainThread {
block()
} else {
@ -20,29 +20,28 @@ func dispatch_sync_safely_main_queue(_ block: ()->()) {
}
extension UIViewController {
func showSnackbar(text:String) {
func showSnackbar(text: String) {
self.snackbarController?.snackbar.text = text
self.snackbarController?.animate(snackbar: .visible)
self.snackbarController?.animate(snackbar: .hidden, delay: 3)
}
}
func NSLocalizedString( _ key:String ) -> String {
func NSLocalizedString(_ key: String) -> String {
return NSLocalizedString(key, comment: "")
}
let kNavigationHeight: CGFloat = {
return kSafeAreaInsets.top + 44
kSafeAreaInsets.top + 44
}()
let kSafeAreaInsets:UIEdgeInsets = {
if #available(iOS 12.0, *){
let kSafeAreaInsets: UIEdgeInsets = {
if #available(iOS 12.0, *) {
return UIWindow().safeAreaInsets
}
else if #available(iOS 11.0, *){
} else if #available(iOS 11.0, *) {
let inset = UIWindow().safeAreaInsets
if inset.top > 0 { return inset}
//iOS 11 safeAreaInsets.top 0iOS12 便
if inset.top > 0 { return inset }
// iOS 11 safeAreaInsets.top 0iOS12 便
}
return UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
}()

View File

@ -7,14 +7,13 @@
//
import Foundation
import MJRefresh
import RxCocoa
import RxSwift
import MJRefresh
extension Reactive where Base : MJRefreshComponent {
extension Reactive where Base: MJRefreshComponent {
var refresh: ControlEvent<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()
guard let control = control else {
observer.onCompleted()
@ -27,10 +26,8 @@ extension Reactive where Base : MJRefreshComponent {
}
return ControlEvent(events: source)
}
}
enum MJRefreshAction {
///
case none
@ -48,16 +45,14 @@ enum MJRefreshAction {
case resetNomoreData
}
extension Reactive where Base:UIScrollView {
extension Reactive where Base: UIScrollView {
///
var refreshAction:Binder<MJRefreshAction> {
return Binder(base) { (target, action) in
switch action{
var refreshAction: Binder<MJRefreshAction> {
return Binder(base) { target, action in
switch action {
case .begainRefresh:
//使 UIRefreshControl
// 使 UIRefreshControl
if let control = target.refreshControl {
control.beginRefreshing()
}
@ -66,26 +61,24 @@ extension Reactive where Base:UIScrollView {
control.endRefreshing()
}
case .begainLoadmore:
if let footer = target.mj_footer {
if let footer = target.mj_footer {
footer.beginRefreshing()
}
case .endLoadmore:
if let footer = target.mj_footer {
if let footer = target.mj_footer {
footer.endRefreshing()
}
case .showNomoreData:
if let footer = target.mj_footer {
if let footer = target.mj_footer {
footer.endRefreshingWithNoMoreData()
}
case .resetNomoreData:
if let footer = target.mj_footer {
if let footer = target.mj_footer {
footer.resetNoMoreData()
}
break
case .none:
break
}
}
}
}

View File

@ -9,21 +9,22 @@
import UIKit
enum BarkApi {
case ping(baseURL:String?)
case register(key:String? , devicetoken:String) //
case ping(baseURL: String?)
case register(key: String?, devicetoken: String) //
}
extension BarkApi: BarkTargetType {
var baseURL: URL {
if case let .ping(urlStr) = self, let url = URL(string: urlStr ?? "") {
if case let .ping(urlStr) = self, let url = URL(string: urlStr ?? "") {
return url
}
return URL(string: ServerManager.shared.currentAddress)!
}
var parameters: [String : Any]? {
var parameters: [String: Any]? {
switch self {
case let .register(key, devicetoken):
var params = ["devicetoken":devicetoken]
var params = ["devicetoken": devicetoken]
if let key = key {
params["key"] = key
}
@ -32,7 +33,7 @@ extension BarkApi: BarkTargetType {
return nil
}
}
var path: String {
switch self {
case .ping:
@ -41,6 +42,4 @@ extension BarkApi: BarkTargetType {
return "/register"
}
}
}

View File

@ -6,21 +6,22 @@
// Copyright © 2018 Fin. All rights reserved.
//
import UIKit
import Moya
import RxSwift
import UIKit
//Providers
fileprivate var retainProviders:[String: Any] = [:]
// Providers
private var retainProviders: [String: Any] = [:]
protocol BarkTargetType: TargetType {
var parameters: [String: Any]? { get }
}
extension BarkTargetType {
var headers: [String : String]? {
var headers: [String: String]? {
return nil
}
var baseURL: URL {
return URL(string: ServerManager.shared.currentAddress)!
}
@ -42,21 +43,19 @@ extension BarkTargetType {
}
var requestTaskWithParameters: Task {
get {
//
var defaultParameters:[String:Any] = [:]
//
if let parameters = self.parameters {
for (key, value) in parameters {
defaultParameters[key] = value
}
//
var defaultParameters: [String: Any] = [:]
//
if let parameters = self.parameters {
for (key, value) in parameters {
defaultParameters[key] = value
}
return Task.requestParameters(parameters: defaultParameters, encoding: parameterEncoding)
}
return Task.requestParameters(parameters: defaultParameters, encoding: parameterEncoding)
}
static var networkActivityPlugin: PluginType {
return NetworkActivityPlugin { (change, type) in
return NetworkActivityPlugin { change, _ in
switch change {
case .began:
dispatch_sync_safely_main_queue {
@ -71,9 +70,9 @@ extension BarkTargetType {
}
/// provider
static var provider: RxSwift.Reactive< MoyaProvider<Self> > {
static var provider: RxSwift.Reactive<MoyaProvider<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
}
let provider = Self.weakProvider
@ -82,18 +81,18 @@ extension BarkTargetType {
}
/// Provider 使
static var weakProvider: RxSwift.Reactive< MoyaProvider<Self> > {
var plugins:[PluginType] = [networkActivityPlugin]
static var weakProvider: RxSwift.Reactive<MoyaProvider<Self>> {
var plugins: [PluginType] = [networkActivityPlugin]
#if DEBUG
plugins.append(LogPlugin())
#endif
let provider = MoyaProvider<Self>(plugins:plugins)
let provider = MoyaProvider<Self>(plugins: plugins)
return provider.rx
}
}
extension RxSwift.Reactive where Base: MoyaProviderType {
public func request(_ token: Base.Target, callbackQueue: DispatchQueue? = nil) -> Observable<Response> {
public extension RxSwift.Reactive where Base: MoyaProviderType {
func request(_ token: Base.Target, callbackQueue: DispatchQueue? = nil) -> Observable<Response> {
return Single.create { [weak base] single in
let cancellableToken = base?.request(token, callbackQueue: callbackQueue, progress: nil) { result in
switch result {
@ -107,11 +106,11 @@ extension RxSwift.Reactive where Base: MoyaProviderType {
return Disposables.create {
cancellableToken?.cancel()
}
}.asObservable()
}.asObservable()
}
}
fileprivate class LogPlugin: PluginType{
private class LogPlugin: PluginType {
func willSend(_ request: RequestType, target: TargetType) {
print("\n-------------------\n准备请求: \(target.path)")
print("请求方式: \(target.method.rawValue)")
@ -119,8 +118,8 @@ fileprivate class LogPlugin: PluginType{
print(params)
}
print("\n")
}
func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
print("\n-------------------\n请求结束: \(target.path)")
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 RxSwift
import ObjectMapper
import SwiftyJSON
import Moya
import ObjectMapper
import RxSwift
import SwiftyJSON
import UIKit
public enum ApiError : Swift.Error {
public enum ApiError: Swift.Error {
case Error(info: String)
case AccountBanned(info: String)
}
@ -25,9 +25,9 @@ extension Swift.Error {
return self.localizedDescription
}
switch err {
case let .Error(info):
case .Error(let info):
return info
case let .AccountBanned(info):
case .AccountBanned(let info):
return info
}
}
@ -35,23 +35,23 @@ extension Swift.Error {
extension Observable where Element: Moya.Response {
/// HTTP
func filterHttpError() -> Observable<Result<Element,ApiError>> {
func filterHttpError() -> Observable<Result<Element, ApiError>> {
return
catchErrorJustReturn(Element(statusCode: 599, data: Data()))
.map { (response) -> Result<Element,ApiError> in
if (200...209) ~= response.statusCode {
return .success(response)
.map { response -> Result<Element, ApiError> in
if (200 ... 209) ~= response.statusCode {
return .success(response)
}
else {
return .failure(ApiError.Error(info: "网络错误"))
}
}
else{
return .failure(ApiError.Error(info: "网络错误"))
}
}
}
/// CODE
func filterResponseError() -> Observable<Result<JSON,ApiError>> {
func filterResponseError() -> Observable<Result<JSON, ApiError>> {
return filterHttpError()
.map{ response -> Result<JSON,ApiError> in
.map { response -> Result<JSON, ApiError> in
switch response {
case .success(let element):
do {
@ -59,11 +59,12 @@ extension Observable where Element: Moya.Response {
if let codeStr = json["code"].rawString(),
let code = Int(codeStr),
code == 200{
code == 200
{
return .success(json)
}
else{
var msg:String = ""
else {
var msg: String = ""
if json["message"].exists() {
msg = json["message"].rawString()!
}
@ -73,7 +74,7 @@ extension Observable where Element: Moya.Response {
catch {
return .failure(ApiError.Error(info: error.rawString()))
}
case .failure(let error) :
case .failure(let error):
return .failure(ApiError.Error(info: error.rawString()))
}
}
@ -84,18 +85,18 @@ extension Observable where Element: Moya.Response {
/// - Parameters:
/// - typeName: Model Class
/// - dataPath: ["data","links"]
func mapResponseToObj<T: Mappable>(_ typeName: T.Type , dataPath:[String] = ["data"] ) -> Observable<Result<T,ApiError>> {
return filterResponseError().map{ json in
func mapResponseToObj<T: Mappable>(_ typeName: T.Type, dataPath: [String] = ["data"]) -> Observable<Result<T, ApiError>> {
return filterResponseError().map { json in
switch json {
case .success(let json):
var rootJson = json
if dataPath.count > 0{
if dataPath.count > 0 {
rootJson = rootJson[dataPath]
}
if let model: T = self.resultFromJSON(json: rootJson) {
if let model: T = self.resultFromJSON(json: rootJson) {
return .success(model)
}
else{
else {
return .failure(ApiError.Error(info: "json 转换失败"))
}
case .failure(let error):
@ -105,24 +106,24 @@ extension Observable where Element: Moya.Response {
}
/// Response JSON Model Array
func mapResponseToObjArray<T: Mappable>(_ type: T.Type, dataPath:[String] = ["data"] ) -> Observable<Result<[T],ApiError>> {
return filterResponseError().map{ json in
func mapResponseToObjArray<T: Mappable>(_ type: T.Type, dataPath: [String] = ["data"]) -> Observable<Result<[T], ApiError>> {
return filterResponseError().map { json in
switch json {
case .success(let json):
var rootJson = json;
if dataPath.count > 0{
var rootJson = json
if dataPath.count > 0 {
rootJson = rootJson[dataPath]
}
var result = [T]()
guard let jsonArray = rootJson.array else{
guard let jsonArray = rootJson.array else {
return .failure(ApiError.Error(info: "Root Json 不是 Array"))
}
for json in jsonArray{
for json in jsonArray {
if let jsonModel: T = self.resultFromJSON(json: json) {
result.append(jsonModel)
}
else{
else {
return .failure(ApiError.Error(info: "json 转换失败"))
}
}
@ -131,15 +132,15 @@ extension Observable where Element: Moya.Response {
case .failure(let error):
return .failure(error)
}
}
}
private func resultFromJSON<T: Mappable>(jsonString:String) -> T? {
private func resultFromJSON<T: Mappable>(jsonString: String) -> T? {
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 nil

View File

@ -5,8 +5,8 @@
// Created by Krunoslav Zaher on 12/6/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import RxSwift
import RxCocoa
import RxSwift
#if os(iOS)
import UIKit
#elseif os(macOS)
@ -14,7 +14,7 @@ import AppKit
#endif
// Two way binding operator between control property and relay, that's all it takes.
infix operator <-> : DefaultPrecedence
infix operator <->: DefaultPrecedence
#if os(iOS)
func nonMarkedText(_ textInput: UITextInput) -> String? {
@ -22,8 +22,9 @@ func nonMarkedText(_ textInput: UITextInput) -> String? {
let end = textInput.endOfDocument
guard let rangeAll = textInput.textRange(from: start, to: end),
let text = textInput.text(in: rangeAll) else {
return nil
let text = textInput.text(in: rangeAll)
else {
return nil
}
guard let markedTextRange = textInput.markedTextRange else {
@ -31,7 +32,8 @@ func nonMarkedText(_ textInput: UITextInput) -> String? {
}
guard let startRange = textInput.textRange(from: start, to: markedTextRange.start),
let endRange = textInput.textRange(from: markedTextRange.end, to: end) else {
let endRange = textInput.textRange(from: markedTextRange.end, to: end)
else {
return text
}
@ -42,7 +44,7 @@ func <-> <Base>(textInput: TextInput<Base>, relay: BehaviorRelay<String>) -> Dis
let bindToUIDisposable = relay.bind(to: textInput.text)
let bindToRelay = textInput.text
.subscribe(onNext: { [weak base = textInput.base] n in
.subscribe(onNext: { [weak base = textInput.base] _ in
guard let base = base else {
return
}
@ -53,7 +55,7 @@ func <-> <Base>(textInput: TextInput<Base>, relay: BehaviorRelay<String>) -> Dis
In some cases `textInput.textRangeFromPosition(start, toPosition: end)` will return nil even though the underlying
value is not nil. This appears to be an Apple bug. If it's not, and we are doing something wrong, please let us know.
The can be reproed easily if replace bottom code with
if nonMarkedTextValue != relay.value {
relay.accept(nonMarkedTextValue ?? "")
}
@ -62,7 +64,7 @@ func <-> <Base>(textInput: TextInput<Base>, relay: BehaviorRelay<String>) -> Dis
if let nonMarkedTextValue = nonMarkedTextValue, nonMarkedTextValue != relay.value {
relay.accept(nonMarkedTextValue)
}
}, onCompleted: {
}, onCompleted: {
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" +
"REMEDY: Just use `textField <-> relay` instead of `textField.rx.text <-> relay`.\n" +
"Find out more here: https://github.com/ReactiveX/RxSwift/issues/649\n"
)
)
#endif
}
@ -85,7 +87,7 @@ func <-> <T>(property: ControlProperty<T>, relay: BehaviorRelay<T>) -> Disposabl
let bindToRelay = property
.subscribe(onNext: { n in
relay.accept(n)
}, onCompleted: {
}, onCompleted: {
bindToUIDisposable.dispose()
})

View File

@ -6,13 +6,13 @@
// Copyright © 2020 Fin. All rights reserved.
//
import UIKit
import RxSwift
import RxCocoa
import RxSwift
import UIKit
private var prepareForReuseBag: Int8 = 0
@objc public protocol Reusable : class {
@objc public protocol Reusable: class {
func prepareForReuse()
}
@ -24,17 +24,17 @@ extension Reactive where Base: Reusable {
var prepareForReuse: Observable<Void> {
return Observable.of(sentMessage(#selector(Base.prepareForReuse)).map { _ in }, deallocated).merge()
}
var reuseBag: DisposeBag {
MainScheduler.ensureExecutingOnScheduler()
if let bag = objc_getAssociatedObject(base, &prepareForReuseBag) as? DisposeBag {
return bag
}
let bag = DisposeBag()
objc_setAssociatedObject(base, &prepareForReuseBag, bag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
_ = sentMessage(#selector(Base.prepareForReuse))
.subscribe(onNext: { [weak base] _ in
guard let strongBase = base else {
@ -43,7 +43,7 @@ extension Reactive where Base: Reusable {
let newBag = DisposeBag()
objc_setAssociatedObject(strongBase, &prepareForReuseBag, newBag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
})
return bag
}
}

View File

@ -12,39 +12,41 @@ let defaultServer = "https://api.day.app"
class ServerManager: NSObject {
static let shared = ServerManager()
private override init() {
if let servers:Set<String> = Settings[.servers] {
override private init() {
if let servers: Set<String> = Settings[.servers] {
self.servers = servers
}
if let address = Settings[.currentServer] {
self.currentAddress = address
}
else{
else {
self.currentAddress = self.servers.first ?? defaultServer
}
super.init()
}
var servers:Set<String> = [defaultServer]
var currentAddress:String {
didSet{
var servers: Set<String> = [defaultServer]
var currentAddress: String {
didSet {
Settings[.currentServer] = currentAddress
}
}
func addServer(server:String){
func addServer(server: String) {
self.servers.insert(server)
Settings[.servers] = self.servers
}
func removeServer(server:String){
func removeServer(server: String) {
self.servers.remove(server)
if self.servers.count <= 0 {
self.servers.insert(defaultServer)
}
Settings[.servers] = self.servers
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

View File

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

View File

@ -9,20 +9,21 @@
import UIKit
extension UIColor {
convenience public init(r255:CGFloat, g255:CGFloat, b255:CGFloat, a255:CGFloat = 255) {
public convenience init(r255: CGFloat, g255: CGFloat, b255: CGFloat, a255: CGFloat = 255) {
self.init(red: r255/255, green: g255/255, blue: b255/255, alpha: a255/255)
}
class func image(color:UIColor, size:CGSize = CGSize(width: 1, height: 1)) -> UIImage{
class func image(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
UIGraphicsBeginImageContext(size)
let context = UIGraphicsGetCurrentContext()
context?.setFillColor(color.cgColor)
context?.fill(CGRect(origin: CGPoint.zero, size: size))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image! //contextget~
return image! // contextget~
}
var image: UIImage {
return UIColor.image(color: self)
}

View File

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

View File

@ -6,11 +6,11 @@
// Copyright © 2018 Fin. All rights reserved.
//
import UIKit
import Material
import RxSwift
import UIKit
class BarkNavigationController: UINavigationController{
class BarkNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationBar.prefersLargeTitles = true
@ -18,7 +18,7 @@ class BarkNavigationController: UINavigationController{
}
class BarkSnackbarController: SnackbarController {
override var childForStatusBarStyle: UIViewController?{
override var childForStatusBarStyle: UIViewController? {
return self.rootViewController
}
}
@ -31,13 +31,12 @@ enum TabPage: Int {
}
class StateStorageTabBarController: UITabBarController, UITabBarControllerDelegate {
//
var currentSelectedIndex: Int = 0
// tabBarItem
lazy var tabBarItemDidClick: Observable<TabPage> = {
return self.rx.didSelect
self.rx.didSelect
.flatMapLatest { _ -> Single<TabPage> in
let single = Single<TabPage>.create { single in
if self.currentSelectedIndex == self.selectedIndex {
@ -55,17 +54,16 @@ class StateStorageTabBarController: UITabBarController, UITabBarControllerDelega
super.viewWillAppear(animated)
if isFirstAppear {
isFirstAppear = false
//APP
if let index:Int = Settings[.selectedViewControllerIndex] {
// APP
if let index: Int = Settings[.selectedViewControllerIndex] {
self.selectedIndex = index
self.currentSelectedIndex = index
}
//Index
self.rx.didSelect.subscribe(onNext: {_ in
// Index
self.rx.didSelect.subscribe(onNext: { _ in
Settings[.selectedViewControllerIndex] = self.selectedIndex
}).disposed(by: rx.disposeBag)
}
}
}

View File

@ -6,10 +6,9 @@
// Copyright © 2018 Fin. All rights reserved.
//
import UIKit
import SafariServices
import UIKit
class BarkSFSafariViewController: SFSafariViewController {
override func viewDidLoad() {
super.viewDidLoad()
@ -20,11 +19,8 @@ class BarkSFSafariViewController: SFSafariViewController {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override var preferredStatusBarStyle: UIStatusBarStyle{
get {
return .default
}
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .default
}
}

View File

@ -6,26 +6,24 @@
// Copyright © 2018 Fin. All rights reserved.
//
import UIKit
import Material
import UIKit
class BaseViewController: UIViewController {
let viewModel:ViewModel
init(viewModel:ViewModel) {
let viewModel: ViewModel
init(viewModel: ViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
self.view.backgroundColor = Color.grey.lighten5
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var preferredStatusBarStyle: UIStatusBarStyle{
get {
return .lightContent
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewDidLoad() {
@ -33,6 +31,7 @@ class BaseViewController: UIViewController {
self.navigationItem.largeTitleDisplayMode = .automatic
makeUI()
}
var isViewModelBinded = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
@ -42,10 +41,7 @@ class BaseViewController: UIViewController {
}
}
func makeUI() {
}
func bindViewModel(){
}
func makeUI() {}
func bindViewModel() {}
}

View File

@ -6,16 +6,15 @@
// Copyright © 2021 Fin. All rights reserved.
//
import UIKit
import Material
import MJRefresh
import RealmSwift
import RxCocoa
import RxDataSources
import MJRefresh
import RxSwift
import UIKit
class GroupFilterViewController: BaseViewController {
let doneButton: BKButton = {
let btn = BKButton()
btn.setTitle(NSLocalizedString("done"), for: .normal)
@ -35,7 +34,7 @@ class GroupFilterViewController: BaseViewController {
}()
let tableView: UITableView = {
let tableView:UITableView = UITableView(frame: CGRect.zero, style: .insetGrouped)
let tableView = UITableView(frame: CGRect.zero, style: .insetGrouped)
tableView.separatorStyle = .singleLine
tableView.separatorColor = Color.grey.lighten3
tableView.backgroundColor = Color.grey.lighten5
@ -49,9 +48,9 @@ class GroupFilterViewController: BaseViewController {
self.view.addSubview(tableView)
self.view.addSubview(showAllGroupsButton)
tableView.snp.makeConstraints { (make) in
tableView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.bottom.equalToSuperview().offset( (kSafeAreaInsets.bottom + 40) * -1)
make.bottom.equalToSuperview().offset((kSafeAreaInsets.bottom + 40) * -1)
make.left.right.equalToSuperview()
}
showAllGroupsButton.snp.makeConstraints { make in
@ -62,6 +61,7 @@ class GroupFilterViewController: BaseViewController {
self.tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 20))
}
override func bindViewModel() {
guard let viewModel = self.viewModel as? GroupFilterViewModel else {
return
@ -71,15 +71,15 @@ class GroupFilterViewController: BaseViewController {
input: GroupFilterViewModel.Input(
showAllGroups: self.showAllGroupsButton.rx
.tap
.compactMap({[weak self] in
guard let strongSelf = self else {return nil}
.compactMap { [weak self] in
guard let strongSelf = self else { return nil }
return !strongSelf.showAllGroupsButton.isSelected
})
}
.asDriver(onErrorDriveWith: .empty()),
doneTap: self.doneButton.rx.tap.asDriver()
))
let dataSource = RxTableViewSectionedReloadDataSource<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 {
return UITableViewCell()
}
@ -95,13 +95,11 @@ class GroupFilterViewController: BaseViewController {
.drive(self.showAllGroupsButton.rx.isSelected)
.disposed(by: rx.disposeBag)
output.dismiss.drive(onNext: {[weak self] in
output.dismiss.drive(onNext: { [weak self] in
self?.dismiss(animated: true, completion: nil)
})
.disposed(by: rx.disposeBag)
}
}
extension GroupFilterViewController: UITableViewDelegate {
}
extension GroupFilterViewController: UITableViewDelegate {}

View File

@ -6,89 +6,86 @@
// Copyright © 2021 Fin. All rights reserved.
//
import UIKit
import RxSwift
import RxDataSources
import RxCocoa
import RealmSwift
import RxCocoa
import RxDataSources
import RxSwift
import UIKit
struct GroupFilterModel {
var name:String?
var checked:Bool
var name: String?
var checked: Bool
}
class GroupFilterViewModel: ViewModel,ViewModelType {
let groups:[GroupFilterModel]
init(groups:[GroupFilterModel]) {
class GroupFilterViewModel: ViewModel, ViewModelType {
let groups: [GroupFilterModel]
init(groups: [GroupFilterModel]) {
self.groups = groups
}
struct Input {
var showAllGroups:Driver<Bool>
var doneTap:Driver<Void>
var showAllGroups: Driver<Bool>
var doneTap: Driver<Void>
}
struct Output {
var groups:Driver<[ SectionModel<String, GroupCellViewModel>]>
var isShowAllGroups:Driver<Bool>
var dismiss:Driver<Void>
var groups: Driver<[SectionModel<String, GroupCellViewModel>]>
var isShowAllGroups: Driver<Bool>
var dismiss: Driver<Void>
}
var done = PublishRelay<[String?]>()
func transform(input: Input) -> Output {
// cellModel
let groupCellModels = self.groups.map({ filterModel in
return GroupCellViewModel(groupFilterModel: filterModel)
})
let groupCellModels = self.groups.map { filterModel in
GroupCellViewModel(groupFilterModel: filterModel)
}
//cell checked
// cell checked
input.showAllGroups.drive(onNext: { isShowAllGroups in
groupCellModels.forEach { model in
groupCellModels.forEach { model in
model.checked.accept(isShowAllGroups)
}
}).disposed(by: rx.disposeBag)
//cell checked
// cell checked
let checkChanged = Observable.merge(groupCellModels.map { model in
return model.checked.asObservable()
model.checked.asObservable()
})
//
let isShowAllGroups =
checkChanged
.map{_ in
return groupCellModels.filter { viewModel in
return viewModel.checked.value
}.count >= groupCellModels.count
}
.map { _ in
groupCellModels.filter { viewModel in
viewModel.checked.value
}.count >= groupCellModels.count
}
input.doneTap.map { () -> [String?] in
let isShowAllGroups = groupCellModels.filter { viewModel in
return viewModel.checked.value
viewModel.checked.value
}.count >= groupCellModels.count
if isShowAllGroups {
return []
}
return groupCellModels
.filter { $0.checked.value}
.filter { $0.checked.value }
.map { $0.name.value }
}
.asObservable()
.bind(to: self.done)
.disposed(by: rx.disposeBag);
.disposed(by: rx.disposeBag)
let dismiss = PublishRelay<Void>()
input.doneTap.map{ _ in () }
input.doneTap.map { _ in () }
.asObservable()
.bind(to: dismiss)
.disposed(by: rx.disposeBag)
return Output(
groups: Driver.just([ SectionModel(model: "header", items: groupCellModels) ]),
groups: Driver.just([SectionModel(model: "header", items: groupCellModels)]),
isShowAllGroups: isShowAllGroups.asDriver(onErrorDriveWith: .empty()),
dismiss: dismiss.asDriver(onErrorDriveWith: .empty())
)
}
}

View File

@ -6,14 +6,13 @@
// Copyright © 2018 Fin. All rights reserved.
//
import UIKit
import UserNotifications
import Material
import RxCocoa
import RxDataSources
import UIKit
import UserNotifications
class HomeViewController: BaseViewController {
let newButton: BKButton = {
let btn = BKButton()
btn.setImage(Icon.add, for: .normal)
@ -24,11 +23,11 @@ class HomeViewController: BaseViewController {
let startButton: FABButton = {
let button = FABButton(title: NSLocalizedString("RegisterDevice"))
button.backgroundColor = Color.white
button.transition([ .scale(0.75) , .opacity(0)] )
button.transition([.scale(0.75), .opacity(0)])
return button
}()
let tableView :UITableView = {
let tableView: UITableView = {
let tableView = UITableView()
tableView.separatorStyle = .none
tableView.backgroundColor = Color.grey.lighten4
@ -44,12 +43,12 @@ class HomeViewController: BaseViewController {
item: UIBarButtonItem(customView: newButton))
self.view.addSubview(self.tableView)
self.tableView.snp.makeConstraints { (make ) in
self.tableView.snp.makeConstraints { make in
make.top.right.bottom.left.equalToSuperview()
}
self.view.addSubview(self.startButton)
self.startButton.snp.makeConstraints { (make) in
self.startButton.snp.makeConstraints { make in
make.width.height.equalTo(150)
make.centerX.equalToSuperview()
make.centerY.equalToSuperview().offset(-50)
@ -57,12 +56,12 @@ class HomeViewController: BaseViewController {
Client.shared.currentTabBarController?
.tabBarItemDidClick
.filter{ $0 == .service }
.subscribe(onNext: {[weak self] index in
.filter { $0 == .service }
.subscribe(onNext: { [weak self] _ in
self?.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
}).disposed(by: self.rx.disposeBag)
}
override func bindViewModel() {
guard let viewModel = self.viewModel as? HomeViewModel else {
return
@ -72,96 +71,95 @@ class HomeViewController: BaseViewController {
input: HomeViewModel.Input(
addCustomServerTap: newButton.rx.tap.asDriver(),
viewDidAppear: self.rx.methodInvoked(#selector(viewDidAppear(_:)))
.map{ _ in () }
.map { _ in () }
.asDriver(onErrorDriveWith: .empty()),
start: self.startButton.rx.tap.asDriver(),
clientState: Client.shared.state.asDriver()
)
)
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String,PreviewCardCellViewModel>> { (source, tableView, indexPath, item) -> UITableViewCell in
if let cell = tableView.dequeueReusableCell(withIdentifier: "\(PreviewCardCell.self)") as? PreviewCardCell{
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, PreviewCardCellViewModel>> { _, tableView, _, item -> UITableViewCell in
if let cell = tableView.dequeueReusableCell(withIdentifier: "\(PreviewCardCell.self)") as? PreviewCardCell {
cell.bindViewModel(model: item)
return cell
}
return UITableViewCell()
}
//
//
output.title
.drive(self.navigationItem.rx.title)
.disposed(by: rx.disposeBag)
//TableView
// TableView
output.previews
.drive(self.tableView.rx.items(dataSource: dataSource))
.disposed(by: rx.disposeBag)
//
//
output.push
.drive(onNext: {[weak self] viewModel in
.drive(onNext: { [weak self] viewModel in
self?.pushViewModel(viewModel: viewModel)
})
.disposed(by: rx.disposeBag)
//ping clienState
// ping clienState
output.clienStateChanged
.drive(Client.shared.state)
.disposed(by: rx.disposeBag)
//
//
output.tableViewHidden
.map{ !$0 }
.map { !$0 }
.drive(self.tableView.rx.isHidden)
.disposed(by: rx.disposeBag)
output.tableViewHidden
.drive(self.startButton.rx.isHidden)
.disposed(by: rx.disposeBag)
//
//
output.registerForRemoteNotifications.drive(onNext: {
UIApplication.shared.registerForRemoteNotifications()
})
.disposed(by: rx.disposeBag)
//
//
output.showSnackbar
.drive(onNext: {[weak self] text in
.drive(onNext: { [weak self] text in
self?.showSnackbar(text: text)
})
.disposed(by: rx.disposeBag)
//startButton
// startButton
output.startButtonEnable
.drive(self.startButton.rx.isEnabled)
.disposed(by: rx.disposeBag)
//
//
output.copy
.drive(onNext: {[weak self] text in
.drive(onNext: { [weak self] text in
UIPasteboard.general.string = text
self?.showSnackbar(text: NSLocalizedString("Copy"))
})
.disposed(by: rx.disposeBag)
//
//
output.preview
.drive(onNext: { url in
UIApplication.shared.open(url, options: [:], completionHandler: nil)
})
.disposed(by: rx.disposeBag)
// TableView
// TableView
output.reloadData
.drive(onNext: {[weak self] in
.drive(onNext: { [weak self] in
self?.tableView.reloadData()
})
.disposed(by: rx.disposeBag)
}
func pushViewModel(viewModel:ViewModel) {
var viewController:UIViewController?
func pushViewModel(viewModel: ViewModel) {
var viewController: UIViewController?
if let viewModel = viewModel as? NewServerViewModel {
viewController = NewServerViewController(viewModel: viewModel)
}
@ -173,5 +171,4 @@ class HomeViewController: BaseViewController {
self.navigationController?.pushViewController(viewController, animated: true)
}
}
}

View File

@ -7,9 +7,9 @@
//
import Foundation
import RxSwift
import RxCocoa
import RxDataSources
import RxSwift
import SwiftyJSON
import UserNotifications
@ -20,8 +20,9 @@ class HomeViewModel: ViewModel, ViewModelType {
let start: Driver<Void>
let clientState: Driver<Client.ClienState>
}
struct Output {
let previews: Driver<[SectionModel<String,PreviewCardCellViewModel>]>
let previews: Driver<[SectionModel<String, PreviewCardCellViewModel>]>
let push: Driver<ViewModel>
let title: Driver<String>
let clienStateChanged: Driver<Client.ClienState>
@ -34,44 +35,46 @@ class HomeViewModel: ViewModel, ViewModelType {
let registerForRemoteNotifications: Driver<Void>
}
let previews:[PreviewModel] = {
return [
let previews: [PreviewModel] = {
[
PreviewModel(
body: NSLocalizedString("CustomedNotificationContent"),
notice: NSLocalizedString("Notice1")),
notice: NSLocalizedString("Notice1")
),
PreviewModel(
title: NSLocalizedString("CustomedNotificationTitle"),
body: NSLocalizedString("CustomedNotificationContent"),
notice: NSLocalizedString("Notice2")),
notice: NSLocalizedString("Notice2")
),
PreviewModel(
body: NSLocalizedString("notificationSound"),
notice: NSLocalizedString("setSounds"),
queryParameter: "sound=minuet",
moreInfo:NSLocalizedString("viewAllSounds"),
moreInfo: NSLocalizedString("viewAllSounds"),
moreViewModel: SoundsViewModel()
),
PreviewModel(
body: NSLocalizedString("archiveNotificationMessageTitle"),
notice: NSLocalizedString("archiveNotificationMessage"),
queryParameter: "isArchive=1"
),
),
PreviewModel(
body: NSLocalizedString("notificationIcon"),
notice: NSLocalizedString("notificationIconNotice"),
queryParameter: "icon=https://day.app/assets/images/avatar.jpg",
image: UIImage(named: "icon")
),
),
PreviewModel(
body: NSLocalizedString("messageGroup"),
notice: NSLocalizedString("groupMessagesNotice"),
queryParameter: "group=groupName",
image: UIImage(named: "group")
),
),
PreviewModel(
body: "URL Test",
notice: NSLocalizedString("urlParameter"),
queryParameter: "url=https://www.baidu.com"
),
),
PreviewModel(
body: "Copy Test",
notice: NSLocalizedString("copyParameter"),
@ -87,36 +90,35 @@ class HomeViewModel: ViewModel, ViewModelType {
}()
func transform(input: Input) -> Output {
let title = BehaviorRelay(value: URL(string: ServerManager.shared.currentAddress)?.host ?? "")
let sectionModel = SectionModel(
model: "previews",
items: previews.map { PreviewCardCellViewModel(previewModel: $0, clientState: input.clientState) })
items: previews.map { PreviewCardCellViewModel(previewModel: $0, clientState: input.clientState) }
)
//
let customServer = input.addCustomServerTap.map { NewServerViewModel() as ViewModel }
//
let customServer = input.addCustomServerTap.map{ NewServerViewModel() as ViewModel }
// title
// title
customServer
.flatMapLatest({ (model) -> Driver<String> in
return (model as! NewServerViewModel).pop.asDriver(onErrorJustReturn: "")
})
.flatMapLatest { model -> Driver<String> in
(model as! NewServerViewModel).pop.asDriver(onErrorJustReturn: "")
}
.drive(title)
.disposed(by: rx.disposeBag)
//previewnotice
let noticeTap = Driver.merge(sectionModel.items.map{ $0.noticeTap.asDriver(onErrorDriveWith: .empty()) })
// previewnotice
let noticeTap = Driver.merge(sectionModel.items.map { $0.noticeTap.asDriver(onErrorDriveWith: .empty()) })
//
let clienState = input.viewDidAppear
.asObservable().flatMapLatest { _ -> Observable<Result<JSON,ApiError>> in
.asObservable().flatMapLatest { _ -> Observable<Result<JSON, ApiError>> in
BarkApi.provider
.request(.ping(baseURL: ServerManager.shared.currentAddress))
.filterResponseError()
}
.map { (response) -> Client.ClienState in
.map { response -> Client.ClienState in
switch response {
case .failure:
return .serverError
@ -125,40 +127,39 @@ class HomeViewModel: ViewModel, ViewModelType {
}
}
//APP
let authorizationStatus = Single<UNAuthorizationStatus>.create { (single) -> Disposable in
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
// APP
let authorizationStatus = Single<UNAuthorizationStatus>.create { single -> Disposable in
UNUserNotificationCenter.current().getNotificationSettings { settings in
single(.success(settings.authorizationStatus))
}
return Disposables.create()
}
.map { $0 == .authorized}
.map { $0 == .authorized }
//
let startRequestAuthorization = Single<Bool>.create { (single) -> Disposable in
//
let startRequestAuthorization = Single<Bool>.create { single -> Disposable in
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert , .sound , .badge], completionHandler: {(_ granted: Bool, _ error: Error?) -> Void in
center.requestAuthorization(options: [.alert, .sound, .badge], completionHandler: { (_ granted: Bool, _: Error?) -> Void in
single(.success(granted))
})
return Disposables.create()
}
.asObservable()
//
//
let tableViewHidden = authorizationStatus
.asObservable()
.concat(input.start
.asObservable()
.flatMapLatest{ startRequestAuthorization })
.asObservable()
.flatMapLatest { startRequestAuthorization })
.asDriver(onErrorJustReturn: false)
let showSnackbar = PublishRelay<String>()
//
//
tableViewHidden
.skip(1)
.compactMap { (granted) -> String? in
.compactMap { granted -> String? in
if !granted {
return NSLocalizedString("AllowNotifications")
}
@ -168,36 +169,35 @@ class HomeViewModel: ViewModel, ViewModelType {
.bind(to: showSnackbar)
.disposed(by: rx.disposeBag)
// viewController
// viewController
let registerForRemoteNotifications = tableViewHidden
.skip(1)
.filter{ $0 }
.map{ _ in () }
.filter { $0 }
.map { _ in () }
//client state
// client state
input.clientState.drive(onNext: { state in
switch state {
case .ok: break;
case .ok: break
case .serverError:
showSnackbar.accept(NSLocalizedString("ServerError"))
default: break;
default: break
}
})
.disposed(by: rx.disposeBag)
return Output(
previews:Driver.just([sectionModel]),
push: Driver<ViewModel>.merge(customServer,noticeTap),
previews: Driver.just([sectionModel]),
push: Driver<ViewModel>.merge(customServer, noticeTap),
title: title.asDriver(),
clienStateChanged: clienState.asDriver(onErrorDriveWith: .empty()),
tableViewHidden: tableViewHidden,
showSnackbar: showSnackbar.asDriver(onErrorDriveWith: .empty()),
startButtonEnable: Driver.just(true),
copy: Driver.merge(sectionModel.items.map{ $0.copy.asDriver(onErrorDriveWith: .empty()) }),
preview: Driver.merge(sectionModel.items.map{ $0.preview.asDriver(onErrorDriveWith: .empty()) }),
reloadData: input.clientState.map{ _ in ()},
copy: Driver.merge(sectionModel.items.map { $0.copy.asDriver(onErrorDriveWith: .empty()) }),
preview: Driver.merge(sectionModel.items.map { $0.preview.asDriver(onErrorDriveWith: .empty()) }),
reloadData: input.clientState.map { _ in () },
registerForRemoteNotifications: registerForRemoteNotifications
)
}
}

View File

@ -6,29 +6,27 @@
// Copyright © 2020 Fin. All rights reserved.
//
import UIKit
import Material
import MJRefresh
import RealmSwift
import RxCocoa
import RxDataSources
import MJRefresh
import RxSwift
import UIKit
enum MessageDeleteType: Int{
enum MessageDeleteType: Int {
case lastHour = 0
case today
case todayAndYesterday
case allTime
var string: String{
get {
return [
NSLocalizedString("lastHour"),
NSLocalizedString("today"),
NSLocalizedString("todayAndYesterday"),
NSLocalizedString("allTime"),
][self.rawValue]
}
var string: String {
return [
NSLocalizedString("lastHour"),
NSLocalizedString("today"),
NSLocalizedString("todayAndYesterday"),
NSLocalizedString("allTime"),
][self.rawValue]
}
}
@ -64,7 +62,7 @@ class MessageListViewController: BaseViewController {
navigationItem.setBarButtonItems(items: [UIBarButtonItem(customView: deleteButton), UIBarButtonItem(customView: groupButton)], left: false)
self.view.addSubview(tableView)
tableView.snp.makeConstraints { (make) in
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
tableView.rx.setDelegate(self).disposed(by: rx.disposeBag)
@ -74,13 +72,12 @@ class MessageListViewController: BaseViewController {
// tab
Client.shared.currentTabBarController?
.tabBarItemDidClick
.filter{ $0 == .messageHistory }
.subscribe(onNext: {[weak self] index in
.filter { $0 == .messageHistory }
.subscribe(onNext: { [weak self] _ in
self?.scrollToTop()
}).disposed(by: self.rx.disposeBag)
//APP1
// APP1
var lastAutoRefreshdate = Date()
NotificationCenter.default.rx
.notification(UIApplication.willEnterForegroundNotification)
@ -92,11 +89,10 @@ class MessageListViewController: BaseViewController {
}
return false
}
.subscribe(onNext: {[weak self] _ in
.subscribe(onNext: { [weak self] _ in
self?.tableView.refreshControl?.sendActions(for: .valueChanged)
self?.scrollToTop()
}).disposed(by: rx.disposeBag)
}
override func bindViewModel() {
@ -106,10 +102,10 @@ class MessageListViewController: BaseViewController {
let batchDelete = deleteButton.rx
.tap
.flatMapLatest { Void -> PublishRelay<MessageDeleteType> in
.flatMapLatest { _ -> PublishRelay<MessageDeleteType> in
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)
alertController.addAction(UIAlertAction(title: NSLocalizedString("clear"), style: .destructive, handler: { _ in
relay.accept(type)
@ -143,67 +139,65 @@ class MessageListViewController: BaseViewController {
loadMore: tableView.mj_footer!.rx.refresh.asDriver(),
itemDelete: tableView.rx.itemDeleted.asDriver(),
itemSelected: tableView.rx.modelSelected(MessageTableViewCellViewModel.self).asDriver(),
delete:batchDelete.asDriver(onErrorDriveWith: .empty()),
delete: batchDelete.asDriver(onErrorDriveWith: .empty()),
groupTap: groupButton.rx.tap.asDriver(),
searchText: navigationItem.searchController!.searchBar.rx.text.asObservable()
))
searchText: navigationItem.searchController!.searchBar.rx.text.asObservable()))
//tableView
// tableView
output.refreshAction
.drive(tableView.rx.refreshAction)
.disposed(by: rx.disposeBag)
//tableView
// tableView
let dataSource = RxTableViewSectionedAnimatedDataSource<MessageSection>(
animationConfiguration: AnimationConfiguration(
insertAnimation: .none,
reloadAnimation: .none,
deleteAnimation: .left),
configureCell:{ (source, tableView, indexPath, item) -> UITableViewCell in
configureCell: { _, tableView, _, item -> UITableViewCell in
guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(MessageTableViewCell.self)") as? MessageTableViewCell else {
return UITableViewCell ()
return UITableViewCell()
}
cell.bindViewModel(model: item)
return cell
}, canEditRowAtIndexPath: { _, _ in
return true
true
})
output.messages
.drive(tableView.rx.items(dataSource: dataSource))
.disposed(by: rx.disposeBag)
//messagealert
output.alertMessage.drive(onNext: {[weak self] message in
// messagealert
output.alertMessage.drive(onNext: { [weak self] message in
self?.alertMessage(message: message)
}).disposed(by: rx.disposeBag)
//messageURL
// messageURL
output.urlTap.drive(onNext: { url in
if ["http","https"].contains(url.scheme?.lowercased() ?? ""){
if ["http", "https"].contains(url.scheme?.lowercased() ?? "") {
self.navigationController?.present(BarkSFSafariViewController(url: url), animated: true, completion: nil)
}
else{
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
else {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}).disposed(by: rx.disposeBag)
//
//
output.groupFilter
.drive(onNext: {[weak self] groupModel in
.drive(onNext: { [weak self] groupModel in
self?.navigationController?.present(BarkNavigationController(rootViewController: GroupFilterViewController(viewModel: groupModel)), animated: true, completion: nil)
}).disposed(by: rx.disposeBag)
//
//
output.title
.drive(self.navigationItem.rx.title).disposed(by: rx.disposeBag)
}
func alertMessage(message:String) {
func alertMessage(message: String) {
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let copyAction = UIAlertAction(title: NSLocalizedString("Copy2"), style: .default, handler: {[weak self]
(alert: UIAlertAction) -> Void in
let copyAction = UIAlertAction(title: NSLocalizedString("Copy2"), style: .default, handler: { [weak self]
(_: UIAlertAction) -> Void in
UIPasteboard.general.string = message
self?.showSnackbar(text: NSLocalizedString("Copy"))
})
@ -216,7 +210,7 @@ class MessageListViewController: BaseViewController {
self.navigationController?.present(alertController, animated: true, completion: nil)
}
private func scrollToTop(){
private func scrollToTop() {
if self.tableView.visibleCells.count > 0 {
self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
}
@ -225,7 +219,7 @@ class MessageListViewController: BaseViewController {
extension MessageListViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let action = UIContextualAction(style: .destructive, title: "删除") {[weak self] (action, sourceView, actionPerformed) in
let action = UIContextualAction(style: .destructive, title: "删除") { [weak self] _, _, actionPerformed in
self?.tableView.dataSource?.tableView?(self!.tableView, commit: .delete, forRowAt: indexPath)
actionPerformed(true)
}
@ -235,14 +229,15 @@ extension MessageListViewController: UITableViewDelegate {
}
}
extension MessageListViewController: UISearchControllerDelegate{
extension MessageListViewController: UISearchControllerDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if self.navigationItem.searchController?.searchBar.isFirstResponder == true{
if self.navigationItem.searchController?.searchBar.isFirstResponder == true {
self.navigationItem.searchController?.searchBar.resignFirstResponder()
}
}
func willDismissSearchController(_ searchController: UISearchController) {
if !searchController.searchBar.isFirstResponder{
if !searchController.searchBar.isFirstResponder {
/*
searchBar searchBar.rx.text
searchBar.rx.text

View File

@ -7,12 +7,12 @@
//
import Foundation
import RxSwift
import RxDataSources
import RxCocoa
import RealmSwift
import RxCocoa
import RxDataSources
import RxSwift
class MessageListViewModel: ViewModel,ViewModelType {
class MessageListViewModel: ViewModel, ViewModelType {
struct Input {
var refresh: Driver<Void>
var loadMore: Driver<Void>
@ -32,10 +32,10 @@ class MessageListViewModel: ViewModel,ViewModelType {
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() {
var results = realm.objects(Message.self)
.filter("isDeleted != true")
@ -60,7 +60,7 @@ class MessageListViewModel: ViewModel,ViewModelType {
guard endIndex > startIndex else {
return []
}
var messages:[Message] = []
var messages: [Message] = []
for i in startIndex ..< endIndex {
messages.append(result[i])
}
@ -70,12 +70,11 @@ class MessageListViewModel: ViewModel,ViewModelType {
return []
}
func transform(input: Input) -> Output {
let alertMessage = input.itemSelected.map { (model) -> String in
let alertMessage = input.itemSelected.map { model -> String in
let message = model.message
var copyContent:String = ""
var copyContent: String = ""
if let title = message.title {
copyContent += "\(title)\n"
}
@ -97,45 +96,45 @@ class MessageListViewModel: ViewModel,ViewModelType {
let refreshAction = BehaviorRelay<MJRefreshAction>(value: .none)
//
let filterGroups: BehaviorRelay<[String?]> = {
if let groups:[String?] = Settings["me.fin.filterGroups"] {
if let groups: [String?] = Settings["me.fin.filterGroups"] {
return BehaviorRelay<[String?]>(value: groups)
}
return BehaviorRelay<[String?]>(value: [])
}()
// Message MessageSection
func messagesToMessageSection(messages:[Message]) -> [MessageSection] {
let cellViewModels = messages.map({ (message) -> MessageTableViewCellViewModel in
return MessageTableViewCellViewModel(message: message)
})
func messagesToMessageSection(messages: [Message]) -> [MessageSection] {
let cellViewModels = messages.map { message -> MessageTableViewCellViewModel in
MessageTableViewCellViewModel(message: message)
}
return [MessageSection(header: "model", messages: cellViewModels)]
}
//
//
filterGroups
.subscribe(onNext: {filterGroups in
.subscribe(onNext: { filterGroups in
if filterGroups.count <= 0 {
titleRelay.accept(NSLocalizedString("historyMessage"))
}
else{
else {
titleRelay.accept(filterGroups.map { $0 ?? NSLocalizedString("default") }.joined(separator: " , "))
}
}).disposed(by: rx.disposeBag)
//
//
Observable
.combineLatest(filterGroups, input.searchText)
.subscribe(onNext: {[weak self] groups, searchText in
.subscribe(onNext: { [weak self] groups, searchText in
self?.results = self?.getResults(filterGroups: groups, searchText: searchText)
}).disposed(by: rx.disposeBag)
//
//
Observable
.merge(
input.refresh.asObservable().map{ () },
filterGroups.map{ _ in () },
input.searchText.asObservable().map{ _ in () }
input.refresh.asObservable().map { () },
filterGroups.map { _ in () },
input.searchText.asObservable().map { _ in () }
)
.subscribe(onNext: {[weak self] in
.subscribe(onNext: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.page = 0
messagesRelay.accept(
@ -146,27 +145,27 @@ class MessageListViewModel: ViewModel,ViewModelType {
refreshAction.accept(.endRefresh)
}).disposed(by: rx.disposeBag)
//
//
input.loadMore.asObservable()
.subscribe(onNext: {[weak self] in
.subscribe(onNext: { [weak self] in
guard let strongSelf = self else { return }
let messages = strongSelf.getNextPage()
let cellViewModels = messages.map({ (message) -> MessageTableViewCellViewModel in
return MessageTableViewCellViewModel(message: message)
})
let cellViewModels = messages.map { message -> MessageTableViewCellViewModel in
MessageTableViewCellViewModel(message: message)
}
refreshAction.accept(.endLoadmore)
if var section = messagesRelay.value.first {
section.messages.append(contentsOf: cellViewModels)
messagesRelay.accept([section])
}
else{
else {
messagesRelay.accept([MessageSection(header: "model", messages: cellViewModels)])
}
}).disposed(by: rx.disposeBag)
//message
input.itemDelete.drive(onNext: {[weak self] indexPath in
// message
input.itemDelete.drive(onNext: { [weak self] indexPath in
if var section = messagesRelay.value.first {
if let realm = try? Realm() {
try? realm.write {
@ -180,19 +179,19 @@ class MessageListViewModel: ViewModel,ViewModelType {
}).disposed(by: rx.disposeBag)
// cell url
let urlTap = messagesRelay.flatMapLatest { (section) -> Observable<String> in
let urlTap = messagesRelay.flatMapLatest { section -> Observable<String> in
if let section = section.first {
let taps = section.messages.compactMap { (model) -> Observable<String> in
return model.urlTap.asObservable()
let taps = section.messages.compactMap { model -> Observable<String> in
model.urlTap.asObservable()
}
return Observable.merge(taps)
}
return .empty()
}
.compactMap { URL(string: $0) } //url
.compactMap { URL(string: $0) } // url
//
input.delete.drive(onNext: {[weak self] type in
//
input.delete.drive(onNext: { [weak self] type in
guard let strongSelf = self else { return }
var date = Date()
@ -211,7 +210,7 @@ class MessageListViewModel: ViewModel,ViewModelType {
guard let messages = strongSelf.getResults(filterGroups: filterGroups.value, searchText: nil)?.filter("createDate >= %@", date) else {
return
}
try? realm.write{
try? realm.write {
for msg in messages {
msg.isDeleted = true
}
@ -223,31 +222,31 @@ class MessageListViewModel: ViewModel,ViewModelType {
}).disposed(by: rx.disposeBag)
//
let groupFilter = input.groupTap.compactMap {() -> GroupFilterViewModel? in
//
let groupFilter = input.groupTap.compactMap { () -> GroupFilterViewModel? in
if let realm = try? Realm() {
let groups = realm.objects(Message.self)
.filter("isDeleted != true")
.distinct(by: ["group"])
.value(forKeyPath: "group") as? [String?]
let groupModels = groups?.compactMap({ groupName -> GroupFilterModel in
let groupModels = groups?.compactMap { groupName -> GroupFilterModel in
var check = true
if filterGroups.value.count > 0 {
check = filterGroups.value.contains(groupName)
}
return GroupFilterModel(name: groupName, checked: check)
})
}
if let models = groupModels {
let viewModel = GroupFilterViewModel(groups: models)
// group
// group
viewModel.done.subscribe(onNext: { filterGroups in
Settings["me.fin.filterGroups"] = filterGroups
}).disposed(by: viewModel.rx.disposeBag)
//
//
viewModel.done
.bind(to: filterGroups)
.disposed(by: viewModel.rx.disposeBag)

View File

@ -6,9 +6,9 @@
// Copyright © 2020 Fin. All rights reserved.
//
import UIKit
import Material
import RxDataSources
import UIKit
class MessageSettingsViewController: BaseViewController {
let tableView: UITableView = {
let tableView = UITableView()
@ -22,14 +22,16 @@ class MessageSettingsViewController: BaseViewController {
return tableView
}()
override func makeUI() {
self.title = NSLocalizedString("settings")
self.view.addSubview(tableView)
tableView.snp.makeConstraints { (make) in
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
override func bindViewModel() {
guard let viewModel = self.viewModel as? MessageSettingsViewModel else {
return
@ -40,9 +42,9 @@ class MessageSettingsViewController: BaseViewController {
)
)
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, MessageSettingItem>> { (source, tableView, indexPath, item) -> UITableViewCell in
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, MessageSettingItem>> { _, tableView, _, item -> UITableViewCell in
switch item {
case .label(let text):
case let .label(text):
if let cell = tableView.dequeueReusableCell(withIdentifier: "\(LabelCell.self)") as? LabelCell {
cell.textLabel?.text = text
return cell
@ -51,12 +53,12 @@ class MessageSettingsViewController: BaseViewController {
if let cell = tableView.dequeueReusableCell(withIdentifier: "\(iCloudStatusCell.self)") {
return cell
}
case .archiveSetting(let viewModel):
case let .archiveSetting(viewModel):
if let cell = tableView.dequeueReusableCell(withIdentifier: "\(ArchiveSettingCell.self)") as? ArchiveSettingCell {
cell.bindViewModel(model: viewModel)
return cell
}
case let .detail(title,text,textColor,_):
case let .detail(title, text, textColor, _):
if let cell = tableView.dequeueReusableCell(withIdentifier: "\(DetailTextCell.self)") as? DetailTextCell {
cell.textLabel?.text = title
cell.detailTextLabel?.text = text
@ -78,10 +80,8 @@ class MessageSettingsViewController: BaseViewController {
.drive(tableView.rx.items(dataSource: dataSource))
.disposed(by: rx.disposeBag)
output.openUrl.drive {[weak self] url in
output.openUrl.drive { [weak self] url in
self?.navigationController?.present(BarkSFSafariViewController(url: url), animated: true, completion: nil)
}.disposed(by: rx.disposeBag)
}
}

View File

@ -7,22 +7,23 @@
//
import Foundation
import RxSwift
import Material
import RxCocoa
import RxDataSources
import Material
import RxSwift
class MessageSettingsViewModel: ViewModel, ViewModelType {
struct Input {
var itemSelected: Driver<MessageSettingItem>
}
struct Output {
var settings:Driver<[SectionModel<String, MessageSettingItem>]>
var openUrl:Driver<URL>
var settings: Driver<[SectionModel<String, MessageSettingItem>]>
var openUrl: Driver<URL>
}
func transform(input: Input) -> Output {
let settings:[MessageSettingItem] = {
let settings: [MessageSettingItem] = {
var settings = [MessageSettingItem]()
settings.append(.label(text: "iCloud"))
settings.append(.iCloudStatus)
@ -32,41 +33,41 @@ class MessageSettingsViewModel: ViewModel, ViewModelType {
settings.append(.label(text: NSLocalizedString("archiveNote")))
if let infoDict = Bundle.main.infoDictionary,
let runId = infoDict["GitHub Run Id"] as? String{
let runId = infoDict["GitHub Run Id"] as? String
{
settings.append(.label(text: NSLocalizedString("buildInfo")))
settings.append(.detail(
title: "Github Run Id",
text:"\(runId)",
textColor: Color.grey.darken2,
url: URL(string: "https://github.com/Finb/Bark/actions/runs/\(runId)")))
title: "Github Run Id",
text: "\(runId)",
textColor: Color.grey.darken2,
url: URL(string: "https://github.com/Finb/Bark/actions/runs/\(runId)")))
settings.append(.label(text: NSLocalizedString("buildDesc")))
}
settings.append(.label(text: NSLocalizedString("other")))
settings.append(.detail(
title: NSLocalizedString("faq"),
text: nil,
textColor: nil,
url: URL(string: "https://day.app/2021/06/barkfaq/")))
title: NSLocalizedString("faq"),
text: nil,
textColor: nil,
url: URL(string: "https://day.app/2021/06/barkfaq/")))
settings.append(.spacer(height: 0.5, color: Color.grey.lighten4))
settings.append(.detail(
title: NSLocalizedString("appSC"),
text: nil,
textColor: nil,
url: URL(string: "https://github.com/Finb/Bark")))
title: NSLocalizedString("appSC"),
text: nil,
textColor: nil,
url: URL(string: "https://github.com/Finb/Bark")))
settings.append(.spacer(height: 0.5, color: Color.grey.lighten4))
settings.append(.detail(
title: NSLocalizedString("backendSC"),
text: nil,
textColor: nil,
url: URL(string: "https://github.com/Finb/bark-server")))
title: NSLocalizedString("backendSC"),
text: nil,
textColor: nil,
url: URL(string: "https://github.com/Finb/bark-server")))
return settings
}()
settings.compactMap { (item) -> ArchiveSettingCellViewModel? in
settings.compactMap { item -> ArchiveSettingCellViewModel? in
if case let MessageSettingItem.archiveSetting(viewModel) = item {
return viewModel
}
@ -74,7 +75,7 @@ class MessageSettingsViewModel: ViewModel, ViewModelType {
}
.first?
.on
.subscribe(onNext: { (on) in
.subscribe(onNext: { on in
ArchiveSettingManager.shared.isArchive = on
}).disposed(by: rx.disposeBag)
@ -85,25 +86,22 @@ class MessageSettingsViewModel: ViewModel, ViewModelType {
return nil
}
return Output(
settings: Driver<[SectionModel<String, MessageSettingItem>]>
.just([SectionModel(model: "model", items: settings)]),
openUrl: openUrl
)
.just([SectionModel(model: "model", items: settings)]),
openUrl: openUrl)
}
}
enum MessageSettingItem {
//
case label(text:String)
case label(text: String)
// iCloud
case iCloudStatus
//
case archiveSetting(viewModel:ArchiveSettingCellViewModel)
case archiveSetting(viewModel: ArchiveSettingCellViewModel)
// cell
case detail(title:String?, text:String?, textColor:UIColor?, url:URL?)
case detail(title: String?, text: String?, textColor: UIColor?, url: URL?)
// 线
case spacer(height:CGFloat, color:UIColor?)
case spacer(height: CGFloat, color: UIColor?)
}

View File

@ -6,22 +6,21 @@
// Copyright © 2018 Fin. All rights reserved.
//
import UIKit
import Material
import SnapKit
import SafariServices
import RxSwift
import RxCocoa
import RxSwift
import SafariServices
import SnapKit
import UIKit
class NewServerViewController: BaseViewController {
let addressTextField : TextField = {
let addressTextField: TextField = {
let textField = TextField()
textField.keyboardType = .URL
textField.placeholder = NSLocalizedString("ServerAddress")
textField.detail = NSLocalizedString("ServerExample")
textField.transition([ .scale(0.85) , .opacity(0)] )
textField.detailLabel.transition([ .scale(0.85) , .opacity(0)] )
textField.transition([.scale(0.85), .opacity(0)])
textField.detailLabel.transition([.scale(0.85), .opacity(0)])
return textField
}()
@ -30,7 +29,7 @@ class NewServerViewController: BaseViewController {
label.text = NSLocalizedString("DeploymentDocuments")
label.textColor = Color.blue.base
label.font = UIFont.systemFont(ofSize: 12)
label.transition([ .scale(0.85) , .opacity(0), .translate(x: 50)] )
label.transition([.scale(0.85), .opacity(0), .translate(x: 50)])
label.isUserInteractionEnabled = true
label.addGestureRecognizer(UITapGestureRecognizer())
return label
@ -52,35 +51,34 @@ class NewServerViewController: BaseViewController {
.top(kNavigationHeight + 40).left(10).right(10)
self.view.addSubview(noticeLabel)
noticeLabel.snp.makeConstraints { (make) in
noticeLabel.snp.makeConstraints { make in
make.top.equalTo(self.addressTextField.snp.bottom).offset(40)
make.left.equalTo(self.addressTextField)
}
}
override func bindViewModel() {
guard let viewModel = self.viewModel as? NewServerViewModel else {
return
}
let noticeTap = noticeLabel.gestureRecognizers!.first!.rx
.event
.map({ (_) -> () in
return ()
})
.map { _ -> () in
()
}
.asDriver(onErrorJustReturn: ())
let done = doneButton.rx.tap
.map({[weak self] in
return self?.addressTextField.text ?? ""
})
.map { [weak self] in
self?.addressTextField.text ?? ""
}
.asDriver(onErrorDriveWith: .empty())
let viewDidAppear = rx
.methodInvoked(#selector(viewDidAppear(_:)))
.map{ _ in () }
.map { _ in () }
.asDriver(onErrorDriveWith: .empty())
let output = viewModel.transform(
input: NewServerViewModel.Input(
noticeClick: noticeTap,
@ -88,36 +86,34 @@ class NewServerViewController: BaseViewController {
viewDidAppear: viewDidAppear
))
//
//
output.showKeyboard.drive(onNext: { [weak self] show in
if show {
_ = self?.addressTextField.becomeFirstResponder()
}
else{
else {
self?.addressTextField.resignFirstResponder()
}
}).disposed(by: rx.disposeBag)
//
output.notice.drive(onNext: {[weak self] url in
//
output.notice.drive(onNext: { [weak self] url in
self?.navigationController?.present(BarkSFSafariViewController(url: url), animated: true, completion: nil)
}).disposed(by: rx.disposeBag)
//URL
// URL
output.urlText
.drive(self.addressTextField.rx.text)
.disposed(by: rx.disposeBag)
//退
output.pop.drive(onNext: {[weak self] _ in
// 退
output.pop.drive(onNext: { [weak self] _ in
self?.navigationController?.popViewController(animated: true)
}).disposed(by: rx.disposeBag)
//
output.showSnackbar.drive(onNext: {[weak self] text in
//
output.showSnackbar.drive(onNext: { [weak self] text in
self?.showSnackbar(text: text)
}).disposed(by: rx.disposeBag)
}
}

View File

@ -7,14 +7,14 @@
//
import Foundation
import RxSwift
import RxCocoa
import SwiftyJSON
import Moya
import RxCocoa
import RxSwift
import SwiftyJSON
class NewServerViewModel: ViewModel, ViewModelType {
struct Input {
var noticeClick:Driver<Void>
var noticeClick: Driver<Void>
var done: Driver<String>
var viewDidAppear: Driver<Void>
}
@ -27,23 +27,21 @@ class NewServerViewModel: ViewModel, ViewModelType {
var pop: Driver<String>
}
private var url: String = ""
let pop = PublishRelay<String>()
func transform(input: Input) -> Output {
let showKeyboard = PublishRelay<Bool>()
let urlText = PublishRelay<String>()
let showSnackbar = PublishRelay<String>()
let notice = input.noticeClick
.map{ URL(string: "https://day.app/2018/06/bark-server-document/")! }
.map { URL(string: "https://day.app/2018/06/bark-server-document/")! }
.asDriver()
input.viewDidAppear
.map{ "https://" }
.map { "https://" }
.asObservable()
.take(1)
.subscribe(onNext: { text in
@ -53,7 +51,7 @@ class NewServerViewModel: ViewModel, ViewModelType {
input.done
.asObservable()
.flatMapLatest {[weak self] (url) -> Observable<Result<JSON,ApiError>> in
.flatMapLatest { [weak self] url -> Observable<Result<JSON, ApiError>> in
showKeyboard.accept(false)
if let _ = URL(string: url) {
guard let strongSelf = self else { return .empty() }
@ -67,7 +65,7 @@ class NewServerViewModel: ViewModel, ViewModelType {
return .empty()
}
}
.subscribe(onNext: {[weak self] response in
.subscribe(onNext: { [weak self] response in
guard let strongSelf = self else { return }
switch response {
case .success:

View File

@ -6,14 +6,14 @@
// Copyright © 2020 Fin. All rights reserved.
//
import UIKit
import Material
import AVKit
import Material
import UIKit
import RxSwift
import NSObject_Rx
import RxCocoa
import RxDataSources
import NSObject_Rx
import RxSwift
class SoundsViewController: BaseViewController {
let tableView: UITableView = {
@ -22,12 +22,12 @@ class SoundsViewController: BaseViewController {
tableView.register(SoundCell.self, forCellReuseIdentifier: "\(SoundCell.self)")
return tableView
}()
override func makeUI() {
self.title = NSLocalizedString("notificationSound")
self.view.addSubview(self.tableView)
self.tableView.snp.makeConstraints { (make) in
self.tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
@ -40,35 +40,36 @@ class SoundsViewController: BaseViewController {
return header
}()
}
override func bindViewModel() {
guard let viewModel = viewModel as? SoundsViewModel else {
return
}
let output = viewModel.transform(
input: SoundsViewModel.Input(soundSelected: self.tableView.rx
.modelSelected(SoundCellViewModel.self)
.asDriver()))
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String,SoundCellViewModel>> { (source, tableView, indexPath, item) -> UITableViewCell in
.modelSelected(SoundCellViewModel.self)
.asDriver()))
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, SoundCellViewModel>> { _, tableView, _, item -> UITableViewCell in
guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(SoundCell.self)") as? SoundCell else {
return UITableViewCell()
}
cell.bindViewModel(model: item)
return cell
}
output.audios
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: rx.disposeBag)
output.copyNameAction.drive(onNext: { name in
UIPasteboard.general.string = name.trimmingCharacters(in: .whitespacesAndNewlines)
self.navigationController?.showSnackbar(text: NSLocalizedString("Copy"))
}).disposed(by: rx.disposeBag)
output.playAction.drive(onNext: { url in
var soundID:SystemSoundID = 0
var soundID: SystemSoundID = 0
AudioServicesCreateSystemSoundID(url, &soundID)
AudioServicesPlaySystemSoundWithCompletion(soundID) {
AudioServicesDisposeSystemSoundID(soundID)

View File

@ -6,19 +6,19 @@
// Copyright © 2020 Fin. All rights reserved.
//
import Foundation
import RxSwift
import RxCocoa
import AVKit
import Foundation
import RxCocoa
import RxDataSources
import RxSwift
class SoundsViewModel:ViewModel,ViewModelType {
class SoundsViewModel: ViewModel, ViewModelType {
struct Input {
var soundSelected:Driver<SoundCellViewModel>
var soundSelected: Driver<SoundCellViewModel>
}
struct Output {
var audios:Observable<[ SectionModel<String, SoundCellViewModel>]>
var audios: Observable<[SectionModel<String, SoundCellViewModel>]>
var copyNameAction: Driver<String>
var playAction: Driver<CFURL>
}
@ -26,15 +26,15 @@ class SoundsViewModel:ViewModel,ViewModelType {
func transform(input: Input) -> Output {
let models = { () -> [AVURLAsset] in
var urls = Bundle.main.urls(forResourcesWithExtension: "caf", subdirectory: nil) ?? []
urls.sort { (u1, u2) -> Bool in
urls.sort { u1, u2 -> Bool in
u1.lastPathComponent.localizedStandardCompare(u2.lastPathComponent) == ComparisonResult.orderedAscending
}
let audios = urls.map { (url) -> AVURLAsset in
let audios = urls.map { url -> AVURLAsset in
let asset = AVURLAsset(url: url)
return asset
}
return audios
}().map { SoundCellViewModel(model: $0 ) }
}().map { SoundCellViewModel(model: $0) }
let copyAction = Driver.merge(
models.map { $0.copyNameAction.asDriver(onErrorDriveWith: .empty()) }
@ -43,8 +43,7 @@ class SoundsViewModel:ViewModel,ViewModelType {
return Output(
audios: Observable.just([SectionModel(model: "model", items: models)]),
copyNameAction: copyAction,
playAction: input.soundSelected.map{ $0.model.url as CFURL }
playAction: input.soundSelected.map { $0.model.url as CFURL }
)
}
}

View File

@ -18,7 +18,6 @@ import Intents
// "Search for messages in <myApp>"
class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling {
override func handler(for intent: INIntent) -> Any {
// This is the default implementation. If you want different objects to handle different intents,
// you can override this and return the handler you want for that particular intent.
@ -31,7 +30,6 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
// Implement resolution methods to provide additional information about your intent (optional).
func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) {
if let recipients = intent.recipients {
// If no recipients were provided we'll need to prompt for a value.
if recipients.count == 0 {
completion([INSendMessageRecipientResolutionResult.needsValue()])
@ -42,7 +40,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
for recipient in recipients {
let matchingContacts = [recipient] // Implement your contact matching logic here to create an array of matching contacts
switch matchingContacts.count {
case 2 ... Int.max:
case 2 ... Int.max:
// We need Siri's help to ask user to pick one from the matches.
resolutionResults += [INSendMessageRecipientResolutionResult.disambiguation(with: matchingContacts)]
@ -56,7 +54,6 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
default:
break
}
}
completion(resolutionResults)
@ -107,9 +104,9 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
identifier: "identifier",
content: "I am so excited about SiriKit!",
dateSent: Date(),
sender: INPerson(personHandle: INPersonHandle(value: "sarah@example.com", type: .emailAddress), nameComponents: nil, displayName: "Sarah", image: nil, contactIdentifier: nil, customIdentifier: nil),
recipients: [INPerson(personHandle: INPersonHandle(value: "+1-415-555-5555", type: .phoneNumber), nameComponents: nil, displayName: "John", image: nil, contactIdentifier: nil, customIdentifier: nil)]
)]
sender: INPerson(personHandle: INPersonHandle(value: "sarah@example.com", type: .emailAddress), nameComponents: nil, displayName: "Sarah", image: nil, contactIdentifier: nil, customIdentifier: nil),
recipients: [INPerson(personHandle: INPersonHandle(value: "+1-415-555-5555", type: .phoneNumber), nameComponents: nil, displayName: "John", image: nil, contactIdentifier: nil, customIdentifier: nil)]
)]
completion(response)
}

View File

@ -6,25 +6,26 @@
// Copyright © 2020 Fin. All rights reserved.
//
import UIKit
import RealmSwift
import IceCream
import RealmSwift
import UIKit
class Message: Object {
@objc dynamic var id = NSUUID().uuidString
@objc dynamic var title:String?
@objc dynamic var body:String?
@objc dynamic var url:String?
@objc dynamic var group:String?
@objc dynamic var createDate:Date?
@objc dynamic var title: String?
@objc dynamic var body: String?
@objc dynamic var url: String?
@objc dynamic var group: String?
@objc dynamic var createDate: Date?
// true IceCream
@objc dynamic var isDeleted = false
override class func primaryKey() -> String? {
return "id"
}
override class func indexedProperties() -> [String] {
return ["group","createDate"]
return ["group", "createDate"]
}
}

View File

@ -10,24 +10,24 @@ import Foundation
import UIKit
class PreviewModel: NSObject {
var title:String?
var body:String?
var category:String?
var notice:String?
var queryParameter:String?
var image:UIImage?
var moreInfo:String?
var moreViewModel:ViewModel?
init(title:String? = nil,
body:String? = nil,
category:String? = nil,
notice:String? = nil,
queryParameter:String? = nil,
image:UIImage? = nil,
moreInfo:String? = nil,
moreViewModel:ViewModel? = nil
) {
var title: String?
var body: String?
var category: String?
var notice: String?
var queryParameter: String?
var image: UIImage?
var moreInfo: String?
var moreViewModel: ViewModel?
init(title: String? = nil,
body: String? = nil,
category: String? = nil,
notice: String? = nil,
queryParameter: String? = nil,
image: UIImage? = nil,
moreInfo: String? = nil,
moreViewModel: ViewModel? = nil)
{
self.title = title
self.body = body
self.category = category

View File

@ -6,75 +6,74 @@
// Copyright © 2018 Fin. All rights reserved.
//
import UIKit
import UserNotifications
import RealmSwift
import Intents
import Kingfisher
import MobileCoreServices
import Intents
import RealmSwift
import UIKit
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> ())?
var contentHandler: ((UNNotificationContent) -> Void)?
lazy var realm:Realm? = {
lazy var realm: Realm? = {
let groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bark")
let fileUrl = groupUrl?.appendingPathComponent("bark.realm")
let config = Realm.Configuration(
fileURL: fileUrl,
schemaVersion: 13,
migrationBlock: { migration, oldSchemaVersion in
migrationBlock: { _, oldSchemaVersion in
// We havent migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
if oldSchemaVersion < 1 {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
})
}
)
// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config
return try? Realm()
}()
///
/// - Parameters:
/// - userInfo:
/// - bestAttemptContentBody: body`` ``
fileprivate func autoCopy(_ userInfo: [AnyHashable : Any], defaultCopy: String) {
fileprivate func autoCopy(_ userInfo: [AnyHashable: Any], defaultCopy: String) {
if userInfo["autocopy"] as? String == "1"
|| userInfo["automaticallycopy"] as? String == "1"{
|| userInfo["automaticallycopy"] as? String == "1"
{
if let copy = userInfo["copy"] as? String {
UIPasteboard.general.string = copy
}
else{
else {
UIPasteboard.general.string = defaultCopy
}
}
}
///
/// - Parameter userInfo:
/// `isarchive` `isarchive`
/// ``
fileprivate func archive(_ userInfo: [AnyHashable : Any]) {
var isArchive:Bool?
if let archive = userInfo["isarchive"] as? String{
fileprivate func archive(_ userInfo: [AnyHashable: Any]) {
var isArchive: Bool?
if let archive = userInfo["isarchive"] as? String {
isArchive = archive == "1" ? true : false
}
if isArchive == nil {
isArchive = ArchiveSettingManager.shared.isArchive
}
let alert = (userInfo["aps"] as? [String:Any])?["alert"] as? [String:Any]
let alert = (userInfo["aps"] as? [String: Any])?["alert"] as? [String: Any]
let title = alert?["title"] as? String
let body = alert?["body"] as? String
let url = userInfo["url"] as? String
let group = userInfo["group"] as? String
if (isArchive == true){
try? realm?.write{
if isArchive == true {
try? realm?.write {
let message = Message()
message.title = title
message.body = body
@ -86,22 +85,21 @@ class NotificationService: UNNotificationServiceExtension {
}
}
///
/// - Parameters:
/// - userInfo:
/// - bestAttemptContent: content
/// - complection:
fileprivate func downloadImage(_ imageUrl:String, complection: @escaping (_ imageFileUrl:String?) -> () ) {
fileprivate func downloadImage(_ imageUrl: String, complection: @escaping (_ imageFileUrl: String?) -> ()) {
guard let groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bark"),
let cache = try? ImageCache(name: "shared",cacheDirectoryURL: groupUrl),
let cache = try? ImageCache(name: "shared", cacheDirectoryURL: groupUrl),
let imageResource = URL(string: imageUrl)
else {
complection(nil)
return
}
func downloadFinished(){
func downloadFinished() {
let cacheFileUrl = cache.cachePath(forKey: imageResource.cacheKey)
complection(cacheFileUrl)
}
@ -119,7 +117,7 @@ class NotificationService: UNNotificationServiceExtension {
return
}
//
cache.storeToDisk(result.originalData, forKey: imageResource.cacheKey, expiration: StorageExpiration.never) { r in
cache.storeToDisk(result.originalData, forKey: imageResource.cacheKey, expiration: StorageExpiration.never) { _ in
downloadFinished()
}
}
@ -130,14 +128,15 @@ class NotificationService: UNNotificationServiceExtension {
/// - bestAttemptContent: content
/// - complection:
fileprivate func setImage(content bestAttemptContent: UNMutableNotificationContent,
complection: @escaping (_ content:UNMutableNotificationContent) -> () ) {
complection: @escaping (_ content: UNMutableNotificationContent) -> ())
{
let userInfo = bestAttemptContent.userInfo
guard let imageUrl = userInfo["image"] as? String else {
complection(bestAttemptContent)
return
}
func finished(_ imageFileUrl: String?){
func finished(_ imageFileUrl: String?) {
guard let imageFileUrl = imageFileUrl else {
complection(bestAttemptContent)
return
@ -146,13 +145,15 @@ class NotificationService: UNNotificationServiceExtension {
// 使
try? FileManager.default.copyItem(
at: URL(fileURLWithPath: imageFileUrl),
to: copyDestUrl)
to: copyDestUrl
)
if let attachment = try? UNNotificationAttachment(
if let attachment = try? UNNotificationAttachment(
identifier: "image",
url: copyDestUrl,
options: [UNNotificationAttachmentOptionsTypeHintKey : kUTTypePNG]){
bestAttemptContent.attachments = [ attachment ]
options: [UNNotificationAttachmentOptionsTypeHintKey: kUTTypePNG]
) {
bestAttemptContent.attachments = [attachment]
}
complection(bestAttemptContent)
}
@ -165,7 +166,8 @@ class NotificationService: UNNotificationServiceExtension {
/// - bestAttemptContent: content
/// - complection:
fileprivate func setIcon(content bestAttemptContent: UNMutableNotificationContent,
complection: @escaping (_ content:UNMutableNotificationContent) -> () ) {
complection: @escaping (_ content: UNMutableNotificationContent) -> ())
{
if #available(iOSApplicationExtension 15.0, *) {
let userInfo = bestAttemptContent.userInfo
guard let imageUrl = userInfo["icon"] as? String else {
@ -173,7 +175,7 @@ class NotificationService: UNNotificationServiceExtension {
return
}
func finished(_ imageFileUrl: String?){
func finished(_ imageFileUrl: String?) {
guard let imageFileUrl = imageFileUrl else {
complection(bestAttemptContent)
return
@ -224,7 +226,8 @@ class NotificationService: UNNotificationServiceExtension {
do {
let content = try bestAttemptContent.updating(from: intent) as! UNMutableNotificationContent
complection(content)
} catch {
}
catch {
// Handle error
}
@ -233,13 +236,12 @@ class NotificationService: UNNotificationServiceExtension {
downloadImage(imageUrl, complection: finished)
}
else{
else {
complection(bestAttemptContent)
}
}
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> ()) {
self.contentHandler = contentHandler
guard let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) else {
contentHandler(request.content)

View File

@ -13,22 +13,25 @@ class ArchiveSettingCell: BaseTableViewCell {
let btn = UISwitch()
return btn
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
self.textLabel?.text = NSLocalizedString("defaultArchiveSettings")
contentView.addSubview(switchButton)
switchButton.snp.makeConstraints { (make) in
switchButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-16)
make.centerY.equalToSuperview()
}
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func bindViewModel(model: ViewModel) {
super.bindViewModel(model: model)
guard let viewModel = model as? ArchiveSettingCellViewModel else {
@ -38,4 +41,3 @@ class ArchiveSettingCell: BaseTableViewCell {
.disposed(by: rx.reuseBag)
}
}

View File

@ -10,7 +10,7 @@ import Foundation
import RxCocoa
class ArchiveSettingCellViewModel: ViewModel {
var on: BehaviorRelay<Bool>
init(on:Bool) {
init(on: Bool) {
self.on = BehaviorRelay<Bool>(value: on)
super.init()
}

View File

@ -8,25 +8,25 @@
import UIKit
protocol AlignmentRectInsetsOverridable:AnyObject {
var alignmentRectInsetsOverride: UIEdgeInsets? {get set}
}
protocol HitTestSlopable:AnyObject {
var hitTestSlop: UIEdgeInsets {get set}
protocol AlignmentRectInsetsOverridable: AnyObject {
var alignmentRectInsetsOverride: UIEdgeInsets? { get set }
}
class BKButton: UIButton, HitTestSlopable,AlignmentRectInsetsOverridable {
var hitTestSlop:UIEdgeInsets = UIEdgeInsets.zero
protocol HitTestSlopable: AnyObject {
var hitTestSlop: UIEdgeInsets { get set }
}
class BKButton: UIButton, HitTestSlopable, AlignmentRectInsetsOverridable {
var hitTestSlop = UIEdgeInsets.zero
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
if hitTestSlop == UIEdgeInsets.zero {
return super.point(inside: point, with:event)
return super.point(inside: point, with: event)
}
else{
else {
return self.bounds.inset(by: hitTestSlop).contains(point)
}
}
var alignmentRectInsetsOverride: UIEdgeInsets?
override var alignmentRectInsets: UIEdgeInsets {
return alignmentRectInsetsOverride ?? super.alignmentRectInsets

View File

@ -9,16 +9,14 @@
import UIKit
class BKLabel: UILabel {
var hitTestSlop = UIEdgeInsets.zero
var hitTestSlop:UIEdgeInsets = UIEdgeInsets.zero
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
if hitTestSlop == UIEdgeInsets.zero {
return super.point(inside: point, with:event)
return super.point(inside: point, with: event)
}
else{
else {
return self.bounds.inset(by: hitTestSlop).contains(point)
}
}
}

View File

@ -9,8 +9,8 @@
import UIKit
class BaseTableViewCell: UITableViewCell {
var viewModel:ViewModel?
func bindViewModel(model:ViewModel){
var viewModel: ViewModel?
func bindViewModel(model: ViewModel) {
self.viewModel = model
}
}

View File

@ -6,16 +6,16 @@
// Copyright © 2021 Fin. All rights reserved.
//
import UIKit
import RxSwift
import RxCocoa
import RxDataSources
import RxSwift
import UIKit
class GroupCellViewModel:ViewModel {
class GroupCellViewModel: ViewModel {
let name = BehaviorRelay<String?>(value: nil)
let checked = BehaviorRelay<Bool>(value: false)
init(groupFilterModel:GroupFilterModel) {
init(groupFilterModel: GroupFilterModel) {
self.name.accept(groupFilterModel.name)
self.checked.accept(groupFilterModel.checked)
}

View File

@ -6,16 +6,17 @@
// Copyright © 2021 Fin. All rights reserved.
//
import UIKit
import Material
import UIKit
class GroupTableViewCell: BaseTableViewCell {
let nameLabel:UILabel = {
let nameLabel: UILabel = {
let label = UILabel()
label.fontSize = 14
label.textColor = Color.darkText.primary
return label
}()
let checkButton: BKButton = {
let btn = BKButton()
btn.setImage(UIImage(named: "baseline_radio_button_unchecked_black_24pt"), for: .normal)
@ -37,31 +38,33 @@ class GroupTableViewCell: BaseTableViewCell {
make.left.equalToSuperview().offset(15)
make.centerY.equalToSuperview()
}
nameLabel.snp.makeConstraints { (make) in
nameLabel.snp.makeConstraints { make in
make.left.equalTo(checkButton.snp.right).offset(15)
make.top.equalToSuperview().offset(15)
make.bottom.equalToSuperview().offset(-15)
}
let tap = UITapGestureRecognizer()
self.contentView.addGestureRecognizer(tap)
tap.rx.event.subscribe(onNext: {[weak self] _ in
tap.rx.event.subscribe(onNext: { [weak self] _ in
(self?.viewModel as? GroupCellViewModel)?.checked.accept(!self!.checkButton.isSelected)
}).disposed(by: rx.disposeBag)
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func bindViewModel(model:ViewModel){
override func bindViewModel(model: ViewModel) {
super.bindViewModel(model: model)
guard let viewModel = model as? GroupCellViewModel else {
return
}
viewModel.name
.map({ name in
return name ?? NSLocalizedString("default")
})
.map { name in
name ?? NSLocalizedString("default")
}
.bind(to: nameLabel.rx.text)
.disposed(by: rx.reuseBag)
@ -70,10 +73,8 @@ class GroupTableViewCell: BaseTableViewCell {
.disposed(by: rx.reuseBag)
viewModel.checked.subscribe(
onNext: {[weak self] checked in
onNext: { [weak self] checked in
self?.checkButton.tintColor = checked ? Color.lightBlue.darken3 : Color.lightGray
}).disposed(by: rx.reuseBag)
}
}

View File

@ -6,19 +6,21 @@
// Copyright © 2020 Fin. All rights reserved.
//
import UIKit
import Material
import UIKit
class LabelCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
self.backgroundColor = Color.grey.lighten5
self.textLabel?.textColor = Color.darkText.secondary
self.textLabel?.fontSize = 12
self.textLabel?.numberOfLines = 0
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

View File

@ -6,10 +6,9 @@
// Copyright © 2020 Fin. All rights reserved.
//
import UIKit
import Material
import UIKit
class MessageTableViewCell: BaseTableViewCell {
let backgroundPanel: UIView = {
let view = UIView()
view.layer.cornerRadius = 3
@ -25,6 +24,7 @@ class MessageTableViewCell: BaseTableViewCell {
label.numberOfLines = 0
return label
}()
let bodyLabel: UILabel = {
let label = UILabel()
label.font = RobotoFont.regular(with: 14)
@ -49,12 +49,14 @@ class MessageTableViewCell: BaseTableViewCell {
label.textColor = Color.darkText.others
return label
}()
let bodyStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
return stackView
}()
let separatorLine:UIImageView = {
let separatorLine: UIImageView = {
let imageView = UIImageView()
imageView.backgroundColor = Color.grey.lighten5
return imageView
@ -80,30 +82,32 @@ class MessageTableViewCell: BaseTableViewCell {
layoutView()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func layoutView(){
bodyStackView.snp.makeConstraints { (make) in
func layoutView() {
bodyStackView.snp.makeConstraints { make in
make.left.top.equalToSuperview().offset(16)
make.right.equalToSuperview().offset(-16)
}
titleLabel.snp.remakeConstraints { (make) in
titleLabel.snp.remakeConstraints { make in
make.left.equalTo(12)
make.right.equalTo(-12)
}
bodyLabel.snp.remakeConstraints { (make) in
bodyLabel.snp.remakeConstraints { make in
make.left.right.equalTo(titleLabel)
}
urlLabel.snp.makeConstraints { (make) in
urlLabel.snp.makeConstraints { make in
make.left.right.equalTo(bodyLabel)
}
dateLabel.snp.remakeConstraints { (make) in
dateLabel.snp.remakeConstraints { make in
make.left.equalTo(bodyLabel)
make.top.equalTo(bodyStackView.snp.bottom).offset(12)
}
separatorLine.snp.remakeConstraints { (make) in
separatorLine.snp.remakeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(dateLabel.snp.bottom).offset(12)
make.height.equalTo(10)
@ -127,12 +131,11 @@ class MessageTableViewCell: BaseTableViewCell {
viewModel.url.bind(to: self.urlLabel.rx.text).disposed(by: rx.reuseBag)
viewModel.date.bind(to: self.dateLabel.rx.text).disposed(by: rx.reuseBag)
viewModel.title.map{ $0.count <= 0}.bind(to: self.titleLabel.rx.isHidden).disposed(by: rx.reuseBag)
viewModel.url.map{ $0.count <= 0}.bind(to: self.urlLabel.rx.isHidden).disposed(by: rx.reuseBag)
viewModel.title.map { $0.count <= 0 }.bind(to: self.titleLabel.rx.isHidden).disposed(by: rx.reuseBag)
viewModel.url.map { $0.count <= 0 }.bind(to: self.urlLabel.rx.isHidden).disposed(by: rx.reuseBag)
self.urlLabel.gestureRecognizers?.first?.rx.event
.map{[weak self] _ in self?.urlLabel.text ?? "" }
.map { [weak self] _ in self?.urlLabel.text ?? "" }
.bind(to: viewModel.urlTap).disposed(by: rx.reuseBag)
}
}

View File

@ -6,12 +6,11 @@
// Copyright © 2020 Fin. All rights reserved.
//
import Differentiator
import Foundation
import RxCocoa
import Differentiator
import RxDataSources
class MessageTableViewCellViewModel: ViewModel {
let message: Message
@ -22,7 +21,7 @@ class MessageTableViewCellViewModel: ViewModel {
let urlTap: PublishRelay<String>
init(message:Message) {
init(message: Message) {
self.message = message
self.title = BehaviorRelay<String>(value: message.title ?? "")
@ -35,13 +34,12 @@ class MessageTableViewCellViewModel: ViewModel {
}
}
struct MessageSection {
var header: String
var messages:[MessageTableViewCellViewModel]
var messages: [MessageTableViewCellViewModel]
}
extension MessageSection:AnimatableSectionModelType {
extension MessageSection: AnimatableSectionModelType {
typealias Item = MessageTableViewCellViewModel
typealias Identity = String
@ -62,7 +60,7 @@ extension MessageSection:AnimatableSectionModelType {
extension MessageTableViewCellViewModel: IdentifiableType {
typealias Identity = String
var identity: String{
var identity: String {
return "\(self.message.id)"
}

View File

@ -6,11 +6,10 @@
// Copyright © 2018 Fin. All rights reserved.
//
import UIKit
import Material
import UIKit
class PreviewCardCell: BaseTableViewCell {
let previewButton = IconButton(image: Icon.cm.skipForward, tintColor: Color.grey.base)
let copyButton = IconButton(image: UIImage(named: "baseline_file_copy_white_24pt"), tintColor: Color.grey.base)
@ -21,6 +20,7 @@ class PreviewCardCell: BaseTableViewCell {
label.numberOfLines = 0
return label
}()
let bodyLabel: UILabel = {
let label = UILabel()
label.font = RobotoFont.regular(with: 14)
@ -37,12 +37,14 @@ class PreviewCardCell: BaseTableViewCell {
label.isUserInteractionEnabled = true
return label
}()
let contentImageView:UIImageView = {
let contentImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
return imageView
}()
let card:UIView = {
let card: UIView = {
let view = UIView()
view.backgroundColor = Color.white
view.layer.cornerRadius = 2
@ -60,6 +62,7 @@ class PreviewCardCell: BaseTableViewCell {
return label
}()
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@ -73,23 +76,22 @@ class PreviewCardCell: BaseTableViewCell {
card.addSubview(copyButton)
card.addSubview(previewButton)
card.snp.makeConstraints { (make) in
card.snp.makeConstraints { make in
make.left.top.equalToSuperview().offset(16)
make.right.equalToSuperview().offset(-16)
make.bottom.equalToSuperview()
}
previewButton.snp.makeConstraints { (make) in
previewButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-10)
make.centerY.equalTo(card.snp.top).offset(40)
make.width.height.equalTo(40)
}
copyButton.snp.makeConstraints { (make) in
copyButton.snp.makeConstraints { make in
make.right.equalTo(previewButton.snp.left).offset(-10)
make.centerY.equalTo(previewButton)
make.width.height.equalTo(40)
}
let titleStackView = UIStackView()
titleStackView.axis = .vertical
titleStackView.addArrangedSubview(titleLabel)
@ -97,20 +99,19 @@ class PreviewCardCell: BaseTableViewCell {
card.addSubview(titleStackView)
titleLabel.snp.makeConstraints { (make) in
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
}
bodyLabel.snp.makeConstraints { (make) in
bodyLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
}
titleStackView.snp.makeConstraints { (make) in
titleStackView.snp.makeConstraints { make in
make.centerY.equalTo(copyButton)
make.left.equalToSuperview()
make.right.equalTo(copyButton.snp.left)
}
let contentStackView = UIStackView()
contentStackView.axis = .vertical
contentStackView.spacing = 20
@ -121,18 +122,18 @@ class PreviewCardCell: BaseTableViewCell {
contentStackView.addArrangedSubview(contentLabel)
contentStackView.addArrangedSubview(noticeLabel)
contentLabel.snp.makeConstraints { (make) in
contentLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12)
make.right.equalToSuperview().offset(-12)
}
contentImageView.snp.remakeConstraints { (make) in
contentImageView.snp.remakeConstraints { make in
make.left.right.equalToSuperview()
}
noticeLabel.snp.makeConstraints { (make) in
noticeLabel.snp.makeConstraints { make in
make.left.equalTo(10)
make.right.equalTo(-10)
}
contentStackView.snp.makeConstraints { (make) in
contentStackView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalTo(previewButton.snp.bottom).offset(20)
make.bottom.equalToSuperview().offset(-10)
@ -141,7 +142,6 @@ class PreviewCardCell: BaseTableViewCell {
noticeLabel.addGestureRecognizer(UITapGestureRecognizer())
}
override func bindViewModel(model: ViewModel) {
guard let viewModel = model as? PreviewCardCellViewModel else {
return
@ -155,7 +155,7 @@ class PreviewCardCell: BaseTableViewCell {
viewModel.notice
.bind(to: self.noticeLabel.rx.attributedText).disposed(by: rx.reuseBag)
viewModel.contentImage
.compactMap{ $0 }
.compactMap { $0 }
.bind(to: self.contentImageView.rx.image)
.disposed(by: rx.reuseBag)
viewModel.contentImage
@ -163,34 +163,33 @@ class PreviewCardCell: BaseTableViewCell {
.bind(to: self.contentImageView.rx.isHidden)
.disposed(by: rx.reuseBag)
//
//
noticeLabel.gestureRecognizers!.first!
.rx.event
.compactMap{[weak weakModel = viewModel](_) -> ViewModel? in
// moreViewModel
return weakModel?.previewModel.moreViewModel
.compactMap { [weak weakModel = viewModel] _ -> ViewModel? in
// moreViewModel
weakModel?.previewModel.moreViewModel
}
.bind(to: viewModel.noticeTap)
.disposed(by: rx.reuseBag)
//
copyButton.rx.tap.map {[weak self] () -> String in
return self?.contentLabel.text ?? ""
//
copyButton.rx.tap.map { [weak self] () -> String in
self?.contentLabel.text ?? ""
}
.bind(to: viewModel.copy)
.disposed(by: rx.reuseBag)
//
previewButton.rx.tap.compactMap {[weak self] () -> URL? in
//
previewButton.rx.tap.compactMap { [weak self] () -> URL? in
if let urlStr = self?.contentLabel.text?.urlEncoded(),
let url = URL(string: urlStr){
let url = URL(string: urlStr)
{
return url
}
return nil
}
.bind(to: viewModel.preview)
.disposed(by: rx.reuseBag)
}
}

View File

@ -20,8 +20,8 @@ class PreviewCardCellViewModel: ViewModel {
let copy = PublishRelay<String>()
let preview = PublishRelay<URL>()
let previewModel:PreviewModel
init( previewModel:PreviewModel, clientState: Driver<Client.ClienState> ) {
let previewModel: PreviewModel
init(previewModel: PreviewModel, clientState: Driver<Client.ClienState>) {
self.previewModel = previewModel
contentImage = BehaviorRelay<UIImage?>(value: previewModel.image)
@ -39,30 +39,29 @@ class PreviewCardCellViewModel: ViewModel {
// ServerManager.shared.currentAddress Client.shared.key
// viewModel input currentAddress key
// MVC MVVM
clientState.compactMap({[weak self] (_) -> NSAttributedString? in
return self?.contentAttrStr()
})
clientState.compactMap { [weak self] _ -> NSAttributedString? in
self?.contentAttrStr()
}
.drive(content)
.disposed(by: rx.disposeBag)
let noticeStr = "\(previewModel.notice ?? "")"
let noticeAttrStr = NSMutableAttributedString(string: noticeStr, attributes: [
NSAttributedString.Key.foregroundColor: Color.grey.base,
NSAttributedString.Key.font : RobotoFont.regular(with: 12)
NSAttributedString.Key.font: RobotoFont.regular(with: 12)
])
if let moreInfo = previewModel.moreInfo {
noticeAttrStr.append(NSMutableAttributedString(string: " \(moreInfo)", attributes: [
NSAttributedString.Key.foregroundColor: Color.blue.base,
NSAttributedString.Key.font : RobotoFont.regular(with: 12)
NSAttributedString.Key.font: RobotoFont.regular(with: 12)
]))
}
notice.accept(noticeAttrStr)
}
func contentAttrStr() -> NSAttributedString {
var fontSize:CGFloat = 14
var fontSize: CGFloat = 14
if UIScreen.main.bounds.size.width <= 320 {
fontSize = 11
}
@ -70,31 +69,31 @@ class PreviewCardCellViewModel: ViewModel {
let attrStr = NSMutableAttributedString(string: "")
attrStr.append(NSAttributedString(string: serverUrl.absoluteString, attributes: [
NSAttributedString.Key.foregroundColor: Color.grey.darken4,
NSAttributedString.Key.font : RobotoFont.regular(with: fontSize)
]))
NSAttributedString.Key.font: RobotoFont.regular(with: fontSize)
]))
attrStr.append(NSAttributedString(string: "/\(Client.shared.key ?? "Your Key")", attributes: [
NSAttributedString.Key.foregroundColor: Color.grey.darken3,
NSAttributedString.Key.font : RobotoFont.regular(with: fontSize)
]))
NSAttributedString.Key.font: RobotoFont.regular(with: fontSize)
]))
if let modelTitle = previewModel.title {
attrStr.append(NSAttributedString(string: "/\(modelTitle)", attributes: [
NSAttributedString.Key.foregroundColor: Color.grey.darken1,
NSAttributedString.Key.font : RobotoFont.regular(with: fontSize)
]))
NSAttributedString.Key.font: RobotoFont.regular(with: fontSize)
]))
}
if let modelBody = previewModel.body {
attrStr.append(NSAttributedString(string: "/\(modelBody)", attributes: [
NSAttributedString.Key.foregroundColor: Color.grey.base,
NSAttributedString.Key.font : RobotoFont.regular(with: fontSize)
]))
NSAttributedString.Key.font: RobotoFont.regular(with: fontSize)
]))
}
if let queryParameter = previewModel.queryParameter {
attrStr.append(NSAttributedString(string: "?\(queryParameter)", attributes: [
NSAttributedString.Key.foregroundColor: Color.grey.lighten1,
NSAttributedString.Key.font : RobotoFont.regular(with: fontSize)
]))
NSAttributedString.Key.font: RobotoFont.regular(with: fontSize)
]))
}
return attrStr

View File

@ -6,24 +6,26 @@
// Copyright © 2020 Fin. All rights reserved.
//
import UIKit
import Material
import AVKit
import Material
import UIKit
class SoundCell: BaseTableViewCell {
let copyButton = IconButton(image: UIImage(named: "baseline_file_copy_white_24pt"), tintColor: Color.grey.base)
let nameLabel:UILabel = {
let nameLabel: UILabel = {
let label = UILabel()
label.fontSize = 14
label.textColor = Color.darkText.primary
return label
}()
let durationLabel:UILabel = {
let durationLabel: UILabel = {
let label = UILabel()
label.fontSize = 12
label.textColor = Color.darkText.secondary
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
@ -32,25 +34,27 @@ class SoundCell: BaseTableViewCell {
self.contentView.addSubview(durationLabel)
self.contentView.addSubview(copyButton)
nameLabel.snp.makeConstraints { (make) in
nameLabel.snp.makeConstraints { make in
make.left.top.equalToSuperview().offset(15)
}
durationLabel.snp.makeConstraints { (make) in
durationLabel.snp.makeConstraints { make in
make.left.equalTo(nameLabel)
make.top.equalTo(nameLabel.snp.bottom).offset(5)
make.bottom.equalToSuperview().offset(-15)
}
copyButton.snp.makeConstraints { (make) in
copyButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-15)
make.centerY.equalToSuperview()
make.width.height.equalTo(40)
}
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func bindViewModel(model:ViewModel){
override func bindViewModel(model: ViewModel) {
super.bindViewModel(model: model)
guard let viewModel = model as? SoundCellViewModel else {
return
@ -60,12 +64,12 @@ class SoundCell: BaseTableViewCell {
.bind(to: nameLabel.rx.text)
.disposed(by: rx.reuseBag)
viewModel.duration
.map { String(format: "%.2g second(s)", CMTimeGetSeconds($0) ) }
.map { String(format: "%.2g second(s)", CMTimeGetSeconds($0)) }
.bind(to: durationLabel.rx.text)
.disposed(by: rx.reuseBag)
copyButton.rx.tap
.map{ viewModel.name.value }
.map { viewModel.name.value }
.bind(to: viewModel.copyNameAction)
.disposed(by: rx.reuseBag)
}

View File

@ -6,18 +6,18 @@
// Copyright © 2020 Fin. All rights reserved.
//
import Foundation
import RxSwift
import RxCocoa
import AVKit
import Foundation
import RxCocoa
import RxSwift
class SoundCellViewModel:ViewModel {
class SoundCellViewModel: ViewModel {
let name = BehaviorRelay<String>(value: "")
let duration = BehaviorRelay<CMTime>(value: .zero)
let copyNameAction = PublishRelay<String>()
let playAction = PublishRelay<CFURL>()
let model: AVURLAsset
init(model: AVURLAsset) {
self.model = model

View File

@ -9,18 +9,21 @@
import UIKit
class SpacerCell: UITableViewCell {
var height:CGFloat = 0 {
didSet{
var height: CGFloat = 0 {
didSet {
self.contentView.snp.remakeConstraints { make in
make.height.equalTo(height)
}
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.backgroundColor = UIColor.clear
self.selectionStyle = .none
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

View File

@ -9,14 +9,14 @@
import UIKit
class DetailTextCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .value1, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
self.accessoryType = .disclosureIndicator
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -8,40 +8,40 @@
import UIKit
// item UIBarButtonItem 8
//16
// fixedSpace UIBarButtonItem
// AlignmentRectInsetsOverridable /
// HitTestSlopable
// item UIBarButtonItem 8
// 16
// fixedSpace UIBarButtonItem
// AlignmentRectInsetsOverridable /
// HitTestSlopable
extension UINavigationItem {
func setLeftBarButtonItem(item: UIBarButtonItem){
func setLeftBarButtonItem(item: UIBarButtonItem) {
setBarButtonItems(items: [item], left: true)
}
func setRightBarButtonItem(item: UIBarButtonItem){
func setRightBarButtonItem(item: UIBarButtonItem) {
setBarButtonItems(items: [item], left: false)
}
func setBarButtonItems(items: [UIBarButtonItem], left:Bool){
func setBarButtonItems(items: [UIBarButtonItem], left: Bool) {
guard items.count > 0 else {
self.leftBarButtonItems = nil
return
}
var buttonItems = items
if #available(iOS 11.0, *) {
buttonItems.forEach { (item) in
guard let view = item.customView else {return}
buttonItems.forEach { item in
guard let view = item.customView else { return }
item.customView?.translatesAutoresizingMaskIntoConstraints = false
(item.customView as? HitTestSlopable)?.hitTestSlop = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
(item.customView as? AlignmentRectInsetsOverridable)?.alignmentRectInsetsOverride = UIEdgeInsets(top: 0, left: left ? 8 : -8, bottom: 0, right: left ? -8 : 8)
item.customView?.snp.makeConstraints({ (make) in
item.customView?.snp.makeConstraints { make in
make.width.equalTo(view.bounds.size.width > 24 ? view.bounds.width : 24)
make.height.equalTo(view.bounds.size.height > 24 ? view.bounds.height : 24)
})
}
}
buttonItems.insert(UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil), at: 0)
}
else{
else {
let spacer = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
spacer.width = -8
buttonItems.insert(spacer, at: 0)

View File

@ -6,22 +6,22 @@
// Copyright © 2020 Fin. All rights reserved.
//
import UIKit
import CloudKit
import UIKit
class iCloudStatusCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .value1, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
self.textLabel?.text = NSLocalizedString("iCloudSatatus")
self.detailTextLabel?.text = ""
CKContainer.default().accountStatus { (status, error) in
CKContainer.default().accountStatus { status, _ in
dispatch_sync_safely_main_queue {
switch status {
case .available:
self.detailTextLabel?.text = NSLocalizedString("available")
case .noAccount, .restricted:
self.detailTextLabel?.text = NSLocalizedString("restricted")
case .couldNotDetermine:
@ -32,6 +32,8 @@ class iCloudStatusCell: UITableViewCell {
}
}
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

View File

@ -11,59 +11,58 @@ import UserNotifications
import UserNotificationsUI
class NotificationViewController: UIViewController, UNNotificationContentExtension {
let noticeLabel:UILabel = {
let noticeLabel: UILabel = {
let label = UILabel()
label.textColor = UIColor.init(named: "notification_copy_color")
label.textColor = UIColor(named: "notification_copy_color")
label.text = NSLocalizedString("Copy", comment: "")
label.font = UIFont.systemFont(ofSize: 16)
label.textAlignment = .center
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.noticeLabel)
self.preferredContentSize = CGSize(width: 0, height: 1)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.preferredContentSize = CGSize(width: 0, height: 1)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.preferredContentSize = CGSize(width: 0, height: 1)
}
func didReceive(_ notification: UNNotification) {
guard notification.request.content.userInfo["autocopy"] as? String == "1"
|| notification.request.content.userInfo["automaticallycopy"] as? String == "1" else {
|| notification.request.content.userInfo["automaticallycopy"] as? String == "1"
else {
return
}
if let copy = notification.request.content.userInfo["copy"] as? String
{
if let copy = notification.request.content.userInfo["copy"] as? String {
UIPasteboard.general.string = copy
}
else{
else {
UIPasteboard.general.string = notification.request.content.body
}
}
func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
let userInfo = response.notification.request.content.userInfo
if let copy = userInfo["copy"] as? String {
UIPasteboard.general.string = copy
}
else{
else {
UIPasteboard.general.string = response.notification.request.content.body
}
self.preferredContentSize = CGSize(width: 0, height: 40)
self.noticeLabel.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 40)
completion(.doNotDismiss)
completion(.doNotDismiss)
}
}