格式化代码

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)
@ -46,7 +46,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Realm()
// Realm()
setupRealm()
self.window = UIWindow(frame: UIScreen.main.bounds)
@ -62,15 +62,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
)
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]
}
@ -83,7 +83,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
], 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()
@ -91,7 +91,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
}
//
//
let bar = UINavigationBar.appearance(whenContainedInInstancesOf: [BarkNavigationController.self])
bar.backIndicatorImage = UIImage(named: "back")
bar.backIndicatorTransitionMaskImage = UIImage(named: "back")
@ -108,51 +108,52 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
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
@ -186,7 +187,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
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
}
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 {
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 {
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,9 +60,9 @@ 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
@ -72,14 +70,14 @@ class Client: NSObject {
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,16 +9,15 @@
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!
@ -49,15 +48,19 @@ extension 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)
}
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> {
var refreshAction: Binder<MJRefreshAction> {
return Binder(base) { target, action in
return Binder(base) { (target, action) in
switch action{
switch action {
case .begainRefresh:
//使 UIRefreshControl
// 使 UIRefreshControl
if let control = target.refreshControl {
control.beginRefreshing()
}
@ -81,11 +76,9 @@ extension Reactive where Base:UIScrollView {
if let footer = target.mj_footer {
footer.resetNoMoreData()
}
break
case .none:
break
}
}
}
}

View File

@ -9,8 +9,8 @@
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 {
@ -20,10 +20,11 @@ extension BarkApi: BarkTargetType {
}
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
}
@ -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,10 +43,9 @@ extension BarkTargetType {
}
var requestTaskWithParameters: Task {
get {
//
var defaultParameters:[String:Any] = [:]
//
//
var defaultParameters: [String: Any] = [:]
//
if let parameters = self.parameters {
for (key, value) in parameters {
defaultParameters[key] = value
@ -53,10 +53,9 @@ extension BarkTargetType {
}
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 {
@ -111,7 +110,7 @@ extension RxSwift.Reactive where Base: MoyaProviderType {
}
}
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 {
.map { response -> Result<Element, ApiError> in
if (200 ... 209) ~= response.statusCode {
return .success(response)
}
else{
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) {
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,7 +22,8 @@ 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 {
let text = textInput.text(in: rangeAll)
else {
return nil
}
@ -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
}

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

View File

@ -12,32 +12,33 @@ 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)
@ -45,6 +46,7 @@ class ServerManager: NSObject {
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,10 +9,11 @@
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)
@ -20,7 +21,7 @@ extension UIColor {
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image! //contextget~
return image! // contextget~
}
var image: UIImage {

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 {
@ -56,16 +55,15 @@ class StateStorageTabBarController: UITabBarController, UITabBarControllerDelega
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()
@ -21,10 +20,7 @@ class BarkSFSafariViewController: SFSafariViewController {
// Dispose of any resources that can be recreated.
}
override var preferredStatusBarStyle: UIStatusBarStyle{
get {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .default
}
}
}

View File

@ -6,33 +6,32 @@
// 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 {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
override func viewDidLoad() {
super.viewDidLoad()
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 makeUI() {}
}
func bindViewModel(){
}
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
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
.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,20 +35,22 @@ 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(
@ -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 })
.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,22 +6,21 @@
// 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 {
var string: String {
return [
NSLocalizedString("lastHour"),
NSLocalizedString("today"),
@ -29,7 +28,6 @@ enum MessageDeleteType: Int{
NSLocalizedString("allTime"),
][self.rawValue]
}
}
}
class MessageListViewController: BaseViewController {
@ -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{
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>
}
func transform(input: Input) -> Output {
let settings:[MessageSettingItem] = {
struct Output {
var settings: Driver<[SectionModel<String, MessageSettingItem>]>
var openUrl: Driver<URL>
}
func transform(input: Input) -> Output {
let settings: [MessageSettingItem] = {
var settings = [MessageSettingItem]()
settings.append(.label(text: "iCloud"))
settings.append(.iCloudStatus)
@ -32,17 +33,17 @@ 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)",
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"),
@ -66,7 +67,7 @@ class MessageSettingsViewModel: ViewModel, ViewModelType {
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
)
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 = {
@ -27,7 +27,7 @@ class SoundsViewController: BaseViewController {
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,6 +40,7 @@ class SoundsViewController: BaseViewController {
return header
}()
}
override func bindViewModel() {
guard let viewModel = viewModel as? SoundsViewModel else {
return
@ -50,7 +51,7 @@ class SoundsViewController: BaseViewController {
.modelSelected(SoundCellViewModel.self)
.asDriver()))
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String,SoundCellViewModel>> { (source, tableView, indexPath, item) -> UITableViewCell in
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, SoundCellViewModel>> { _, tableView, _, item -> UITableViewCell in
guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(SoundCell.self)") as? SoundCell else {
return UITableViewCell()
}
@ -68,7 +69,7 @@ class SoundsViewController: BaseViewController {
}).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()])
@ -56,7 +54,6 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
default:
break
}
}
completion(resolutionResults)

View File

@ -6,16 +6,16 @@
// 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
@ -23,8 +23,9 @@ class Message: Object {
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?
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
) {
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(
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,6 +13,7 @@ class ArchiveSettingCell: BaseTableViewCell {
let btn = UISwitch()
return btn
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
@ -20,11 +21,13 @@ class ArchiveSettingCell: BaseTableViewCell {
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")
}
@ -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,21 +8,21 @@
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 {
protocol HitTestSlopable: AnyObject {
var hitTestSlop: UIEdgeInsets { get set }
}
var hitTestSlop:UIEdgeInsets = UIEdgeInsets.zero
class BKButton: UIButton, HitTestSlopable, AlignmentRectInsetsOverridable {
var hitTestSlop = UIEdgeInsets.zero
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
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,16 +9,14 @@
import UIKit
class BKLabel: UILabel {
var hitTestSlop:UIEdgeInsets = UIEdgeInsets.zero
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)
}
}
}

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,8 +6,8 @@
// Copyright © 2020 Fin. All rights reserved.
//
import UIKit
import Material
import UIKit
class LabelCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
@ -19,6 +19,8 @@ class LabelCell: UITableViewCell {
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,30 +69,30 @@ 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)
]))
}

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,12 +6,12 @@
// 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)

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,8 +6,8 @@
// Copyright © 2020 Fin. All rights reserved.
//
import UIKit
import CloudKit
import UIKit
class iCloudStatusCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
@ -16,7 +16,7 @@ class iCloudStatusCell: UITableViewCell {
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:
@ -32,6 +32,8 @@ class iCloudStatusCell: UITableViewCell {
}
}
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

View File

@ -11,10 +11,9 @@ 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
@ -26,10 +25,12 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi
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)
@ -37,26 +38,25 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi
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
}
@ -64,6 +64,5 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi
self.noticeLabel.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 40)
completion(.doNotDismiss)
}
}