支持多服务器
@ -14,6 +14,8 @@
|
||||
060481EE250F404500BC9799 /* SoundsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 060481ED250F404500BC9799 /* SoundsViewController.swift */; };
|
||||
060481F0250F51CA00BC9799 /* SoundCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 060481EF250F51CA00BC9799 /* SoundCell.swift */; };
|
||||
0604F7DF20620D4900B32F09 /* ServerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0604F7DE20620D4900B32F09 /* ServerManager.swift */; };
|
||||
06172FDA27F6DAEF002333A4 /* ServerListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06172FD927F6DAEF002333A4 /* ServerListTableViewCell.swift */; };
|
||||
06172FDC27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06172FDB27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift */; };
|
||||
062B98C3251B2762004562E7 /* BKButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 062B98C2251B2762004562E7 /* BKButton.swift */; };
|
||||
062B98C8251B27AE004562E7 /* UINavigationItem+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 062B98C7251B27AE004562E7 /* UINavigationItem+Extension.swift */; };
|
||||
0632050F250B6DD4001561EC /* gotosleep.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204F0250B6DD1001561EC /* gotosleep.caf */; };
|
||||
@ -88,6 +90,8 @@
|
||||
06802E5320ECC40C00767047 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0661A549204FDA4100965E4E /* Assets.xcassets */; };
|
||||
06840DBB272298FB001B3193 /* BKColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06840DBA272298FB001B3193 /* BKColor.swift */; };
|
||||
06885EB6247FB9880004A303 /* MessageSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06885EB5247FB9880004A303 /* MessageSettingsViewController.swift */; };
|
||||
068EC15827ED99C900D5D11E /* ServerListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068EC15727ED99C900D5D11E /* ServerListViewController.swift */; };
|
||||
068EC15A27ED99E700D5D11E /* ServerListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068EC15927ED99E700D5D11E /* ServerListViewModel.swift */; };
|
||||
068F66B3247BD84C00DAD25A /* MessageListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068F66B2247BD84C00DAD25A /* MessageListViewController.swift */; };
|
||||
06AE3118266F4E2E00B39FBB /* GroupFilterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AE3117266F4E2E00B39FBB /* GroupFilterViewController.swift */; };
|
||||
06AE311A266F4E6600B39FBB /* GroupFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AE3119266F4E6600B39FBB /* GroupFilterViewModel.swift */; };
|
||||
@ -174,6 +178,8 @@
|
||||
060481ED250F404500BC9799 /* SoundsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundsViewController.swift; sourceTree = "<group>"; };
|
||||
060481EF250F51CA00BC9799 /* SoundCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundCell.swift; sourceTree = "<group>"; };
|
||||
0604F7DE20620D4900B32F09 /* ServerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerManager.swift; sourceTree = "<group>"; };
|
||||
06172FD927F6DAEF002333A4 /* ServerListTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListTableViewCell.swift; sourceTree = "<group>"; };
|
||||
06172FDB27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListTableViewCellViewModel.swift; sourceTree = "<group>"; };
|
||||
062B98C2251B2762004562E7 /* BKButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BKButton.swift; sourceTree = "<group>"; };
|
||||
062B98C7251B27AE004562E7 /* UINavigationItem+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationItem+Extension.swift"; sourceTree = "<group>"; };
|
||||
063204F0250B6DD1001561EC /* gotosleep.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = gotosleep.caf; sourceTree = "<group>"; };
|
||||
@ -252,6 +258,8 @@
|
||||
0683487220510FB20024B6DA /* UserNotificationsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotificationsUI.framework; path = System/Library/Frameworks/UserNotificationsUI.framework; sourceTree = SDKROOT; };
|
||||
06840DBA272298FB001B3193 /* BKColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BKColor.swift; sourceTree = "<group>"; };
|
||||
06885EB5247FB9880004A303 /* MessageSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSettingsViewController.swift; sourceTree = "<group>"; };
|
||||
068EC15727ED99C900D5D11E /* ServerListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListViewController.swift; sourceTree = "<group>"; };
|
||||
068EC15927ED99E700D5D11E /* ServerListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListViewModel.swift; sourceTree = "<group>"; };
|
||||
068F66B2247BD84C00DAD25A /* MessageListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageListViewController.swift; sourceTree = "<group>"; };
|
||||
06AE3117266F4E2E00B39FBB /* GroupFilterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupFilterViewController.swift; sourceTree = "<group>"; };
|
||||
06AE3119266F4E6600B39FBB /* GroupFilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupFilterViewModel.swift; sourceTree = "<group>"; };
|
||||
@ -356,6 +364,8 @@
|
||||
06AE3117266F4E2E00B39FBB /* GroupFilterViewController.swift */,
|
||||
06AE3119266F4E6600B39FBB /* GroupFilterViewModel.swift */,
|
||||
06F11E7627D9D5FB00F00298 /* QRScannerViewController.swift */,
|
||||
068EC15727ED99C900D5D11E /* ServerListViewController.swift */,
|
||||
068EC15927ED99E700D5D11E /* ServerListViewModel.swift */,
|
||||
);
|
||||
path = Controller;
|
||||
sourceTree = "<group>";
|
||||
@ -383,6 +393,8 @@
|
||||
06C2CF242685BDB80034B127 /* SpacerCell.swift */,
|
||||
0642B55927EB13F100453D91 /* MutableTextCell.swift */,
|
||||
0642B55B27EB149900453D91 /* MutableTextCellViewModel.swift */,
|
||||
06172FD927F6DAEF002333A4 /* ServerListTableViewCell.swift */,
|
||||
06172FDB27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
@ -941,8 +953,11 @@
|
||||
0603706D20E23EC000F4CA05 /* BarkSFSafariViewController.swift in Sources */,
|
||||
06AE311A266F4E6600B39FBB /* GroupFilterViewModel.swift in Sources */,
|
||||
06C5953124811392006B98F3 /* ArchiveSettingCell.swift in Sources */,
|
||||
068EC15A27ED99E700D5D11E /* ServerListViewModel.swift in Sources */,
|
||||
06172FDC27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift in Sources */,
|
||||
065BE4502563D939002A8CA4 /* SoundCellViewModel.swift in Sources */,
|
||||
06B1158F247BB1FB006D91FB /* Message.swift in Sources */,
|
||||
06172FDA27F6DAEF002333A4 /* ServerListTableViewCell.swift in Sources */,
|
||||
06C595362481160F006B98F3 /* BKLabel.swift in Sources */,
|
||||
0637FA7820E0926D00E80174 /* BarkTargetType.swift in Sources */,
|
||||
064CABA6256BE9510018155C /* PreviewModel.swift in Sources */,
|
||||
@ -969,6 +984,7 @@
|
||||
06885EB6247FB9880004A303 /* MessageSettingsViewController.swift in Sources */,
|
||||
06F11E7727D9D5FB00F00298 /* QRScannerViewController.swift in Sources */,
|
||||
06C5952D2480E3F8006B98F3 /* LabelCell.swift in Sources */,
|
||||
068EC15827ED99C900D5D11E /* ServerListViewController.swift in Sources */,
|
||||
0637FA7C20E0930E00E80174 /* BarkApi.swift in Sources */,
|
||||
06C2CF252685BDB80034B127 /* SpacerCell.swift in Sources */,
|
||||
06BBB8BC2567B3AD0076F63E /* ArchiveSettingCellViewModel.swift in Sources */,
|
||||
|
||||
26
Bark/Assets.xcassets/baseline_keyboard_arrow_down_black_24pt.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "baseline_keyboard_arrow_down_black_24pt_1x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "baseline_keyboard_arrow_down_black_24pt_2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "baseline_keyboard_arrow_down_black_24pt_3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 126 B |
|
After Width: | Height: | Size: 186 B |
|
After Width: | Height: | Size: 243 B |
26
Bark/Assets.xcassets/baseline_remove_black_20pt.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "baseline_remove_black_20pt_1x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "baseline_remove_black_20pt_2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "baseline_remove_black_20pt_3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
BIN
Bark/Assets.xcassets/baseline_remove_black_20pt.imageset/baseline_remove_black_20pt_1x.png
vendored
Normal file
|
After Width: | Height: | Size: 83 B |
BIN
Bark/Assets.xcassets/baseline_remove_black_20pt.imageset/baseline_remove_black_20pt_2x.png
vendored
Normal file
|
After Width: | Height: | Size: 107 B |
BIN
Bark/Assets.xcassets/baseline_remove_black_20pt.imageset/baseline_remove_black_20pt_3x.png
vendored
Normal file
|
After Width: | Height: | Size: 111 B |
26
Bark/Assets.xcassets/baseline_wifi_black_20pt.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "baseline_wifi_black_20pt_1x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "baseline_wifi_black_20pt_2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "baseline_wifi_black_20pt_3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
BIN
Bark/Assets.xcassets/baseline_wifi_black_20pt.imageset/baseline_wifi_black_20pt_1x.png
vendored
Normal file
|
After Width: | Height: | Size: 263 B |
BIN
Bark/Assets.xcassets/baseline_wifi_black_20pt.imageset/baseline_wifi_black_20pt_2x.png
vendored
Normal file
|
After Width: | Height: | Size: 455 B |
BIN
Bark/Assets.xcassets/baseline_wifi_black_20pt.imageset/baseline_wifi_black_20pt_3x.png
vendored
Normal file
|
After Width: | Height: | Size: 636 B |
26
Bark/Assets.xcassets/baseline_wifi_off_black_20pt.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "baseline_wifi_off_black_20pt_1x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "baseline_wifi_off_black_20pt_2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "baseline_wifi_off_black_20pt_3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
BIN
Bark/Assets.xcassets/baseline_wifi_off_black_20pt.imageset/baseline_wifi_off_black_20pt_1x.png
vendored
Normal file
|
After Width: | Height: | Size: 304 B |
BIN
Bark/Assets.xcassets/baseline_wifi_off_black_20pt.imageset/baseline_wifi_off_black_20pt_2x.png
vendored
Normal file
|
After Width: | Height: | Size: 523 B |
BIN
Bark/Assets.xcassets/baseline_wifi_off_black_20pt.imageset/baseline_wifi_off_black_20pt_3x.png
vendored
Normal file
|
After Width: | Height: | Size: 726 B |
21
Bark/Assets.xcassets/offline.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "offline@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Bark/Assets.xcassets/offline.imageset/offline@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
21
Bark/Assets.xcassets/online.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "online@3X.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Bark/Assets.xcassets/online.imageset/online@3X.png
vendored
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
@ -105,3 +105,17 @@ badge = "Modify a Notification Badge";
|
||||
badgeNotice = "The notification badge can be set with a number.";
|
||||
|
||||
deviceTokenInfo = "Token for APNs, click to copy to clipboard.";
|
||||
|
||||
|
||||
serverList = "Server List";
|
||||
copyAddressAndKey = "Copy address and key";
|
||||
resetKey = "Reset or Restore Key";
|
||||
resetKeyDesc = "Click confirm to reset, enter the old Key to restore";
|
||||
resetKeyPlaceholder = "Do not enter or enter the old Key";
|
||||
confirm = "Confirm";
|
||||
deleteServer = "Delete server";
|
||||
confirmDeleteServer = "Sure to delete the server?";
|
||||
deleteFailed = "Cannot be deleted, the server must keep at least one";
|
||||
deletedSuccessfully = "Deleted successfully";
|
||||
resetFailed = "Reset failed";
|
||||
resetFailed2 = "Failed to reset, did not get DeviceToken";
|
||||
|
||||
@ -108,3 +108,17 @@ badge = "设置角标";
|
||||
badgeNotice = "可以为推送设置角标,角标可以是任意数字。";
|
||||
|
||||
deviceTokenInfo = "用于 APNs 推送的 Token,请勿泄露,点击可复制到剪切板。";
|
||||
|
||||
|
||||
serverList = "服务器列表";
|
||||
copyAddressAndKey = "复制地址和Key";
|
||||
resetKey = "重置或还原Key";
|
||||
resetKeyDesc = "重置直接点确定,还原请先输入旧的Key";
|
||||
resetKeyPlaceholder = "不输入或输入旧的Key";
|
||||
confirm = "确定";
|
||||
deleteServer = "删除服务器";
|
||||
confirmDeleteServer = "确定删除服务器吗?";
|
||||
deleteFailed = "不能删除,服务器至少需保留一个";
|
||||
deletedSuccessfully = "删除成功";
|
||||
resetFailed = "重置失败";
|
||||
resetFailed2 = "重置失败,没有获取到 DeviceToken";
|
||||
|
||||
@ -81,6 +81,12 @@ class ServerManager: NSObject {
|
||||
saveServers()
|
||||
}
|
||||
|
||||
func updateServerKey(server: Server) {
|
||||
let foundServer = self.servers.first{ $0.id == server.id }
|
||||
foundServer?.key = server.key
|
||||
saveServers()
|
||||
}
|
||||
|
||||
/// 移除 server,移除后如果 server 为`空`, `会新增一个默认server`
|
||||
func removeServer(server: Server) {
|
||||
self.servers.removeAll { $0.id == server.id }
|
||||
@ -88,6 +94,8 @@ class ServerManager: NSObject {
|
||||
self.servers.append(
|
||||
Server(id: UUID().uuidString, address: defaultServer, key: "")
|
||||
)
|
||||
}
|
||||
if self.currentServer.id == server.id {
|
||||
self.setCurrentServer(serverId: self.servers[0].id)
|
||||
}
|
||||
saveServers()
|
||||
@ -140,7 +148,9 @@ class ServerManager: NSObject {
|
||||
.merge(apis)
|
||||
.subscribe { result in
|
||||
// 更新所有的 server 状态
|
||||
result.0.key = result.1
|
||||
if result.2 == .ok {
|
||||
result.0.key = result.1
|
||||
}
|
||||
result.0.state = result.2
|
||||
|
||||
// 通知客户端 当前 server 状态改变
|
||||
|
||||
@ -51,7 +51,7 @@ class HomeViewController: BaseViewController<HomeViewModel> {
|
||||
|
||||
navigationItem.setBarButtonItems(items: [
|
||||
UIBarButtonItem(customView: newButton),
|
||||
// UIBarButtonItem(customView: serversButton),
|
||||
UIBarButtonItem(customView: serversButton),
|
||||
], position: .right)
|
||||
|
||||
self.view.addSubview(self.tableView)
|
||||
@ -98,6 +98,7 @@ class HomeViewController: BaseViewController<HomeViewModel> {
|
||||
let output = viewModel.transform(
|
||||
input: HomeViewModel.Input(
|
||||
addCustomServerTap: newButton.rx.tap.asDriver(),
|
||||
serverListTap: serversButton.rx.tap.asDriver(),
|
||||
viewDidAppear: self.rx.methodInvoked(#selector(viewDidAppear(_:)))
|
||||
.map { _ in () }
|
||||
.asDriver(onErrorDriveWith: .empty()),
|
||||
@ -132,6 +133,11 @@ class HomeViewController: BaseViewController<HomeViewModel> {
|
||||
self?.pushViewModel(viewModel: viewModel)
|
||||
})
|
||||
.disposed(by: rx.disposeBag)
|
||||
output.present
|
||||
.drive(onNext: { [weak self] viewModel in
|
||||
self?.presentViewModel(viewModel: viewModel)
|
||||
})
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
// 通过ping服务器,判断 clienState
|
||||
output.clienStateChanged
|
||||
@ -201,4 +207,13 @@ class HomeViewController: BaseViewController<HomeViewModel> {
|
||||
self.navigationController?.pushViewController(viewController, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
func presentViewModel(viewModel: ViewModel) {
|
||||
if let viewModel = viewModel as? ServerListViewModel {
|
||||
let controller = BarkSnackbarController(
|
||||
rootViewController: BarkNavigationController(
|
||||
rootViewController: ServerListViewController(viewModel: viewModel)))
|
||||
self.navigationController?.present(controller, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import UserNotifications
|
||||
class HomeViewModel: ViewModel, ViewModelType {
|
||||
struct Input {
|
||||
let addCustomServerTap: Driver<Void>
|
||||
let serverListTap: Driver<Void>
|
||||
let viewDidAppear: Driver<Void>
|
||||
let start: Driver<Void>
|
||||
let clientState: Driver<Client.ClienState>
|
||||
@ -26,6 +27,7 @@ class HomeViewModel: ViewModel, ViewModelType {
|
||||
struct Output {
|
||||
let previews: Driver<[SectionModel<String, PreviewCardCellViewModel>]>
|
||||
let push: Driver<ViewModel>
|
||||
let present: Driver<ViewModel>
|
||||
let title: Driver<String>
|
||||
let clienStateChanged: Driver<Client.ClienState>
|
||||
let tableViewHidden: Driver<Bool>
|
||||
@ -178,10 +180,24 @@ class HomeViewModel: ViewModel, ViewModelType {
|
||||
}
|
||||
})
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
|
||||
let serverList = input.serverListTap.map { ServerListViewModel() as ViewModel }
|
||||
|
||||
// 服务器发生了改变
|
||||
serverList
|
||||
.flatMapLatest { model -> Driver<Server> in
|
||||
(model as! ServerListViewModel).currentServerChanged.asDriver(onErrorDriveWith: .empty())
|
||||
}
|
||||
.map { server -> String in
|
||||
(try? server.address.asURL().host) ?? ""
|
||||
}
|
||||
.drive(title)
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
return Output(
|
||||
previews: Driver.just([sectionModel]),
|
||||
push: Driver<ViewModel>.merge(customServer, noticeTap),
|
||||
present: serverList.asDriver(),
|
||||
title: title.asDriver(),
|
||||
clienStateChanged: clienState.asDriver(onErrorDriveWith: .empty()),
|
||||
tableViewHidden: tableViewHidden,
|
||||
|
||||
165
Controller/ServerListViewController.swift
Normal file
@ -0,0 +1,165 @@
|
||||
//
|
||||
// ServerListViewController.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2022/3/25.
|
||||
// Copyright © 2022 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import Material
|
||||
import RxCocoa
|
||||
import RxDataSources
|
||||
import RxSwift
|
||||
import UIKit
|
||||
|
||||
enum ServerActionType {
|
||||
case copy
|
||||
case reset(key: String?)
|
||||
case delete
|
||||
}
|
||||
|
||||
func == (lhs: ServerActionType, rhs: ServerActionType) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.copy, .copy),
|
||||
(.delete, .delete):
|
||||
return true
|
||||
case let (.reset(a), .reset(b)):
|
||||
return a == b
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class ServerListViewController: BaseViewController<ServerListViewModel> {
|
||||
let closeButton: BKButton = {
|
||||
let closeButton = BKButton()
|
||||
closeButton.setImage(UIImage(named: "baseline_keyboard_arrow_down_black_24pt")?.withRenderingMode(.alwaysTemplate), for: .normal)
|
||||
closeButton.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
|
||||
closeButton.hitTestSlop = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
|
||||
closeButton.tintColor = BKColor.grey.darken4
|
||||
return closeButton
|
||||
}()
|
||||
|
||||
let tableView: UITableView = {
|
||||
let tableView = UITableView()
|
||||
tableView.separatorStyle = .none
|
||||
tableView.backgroundColor = BKColor.background.primary
|
||||
tableView.register(ServerListTableViewCell.self, forCellReuseIdentifier: "\(ServerListTableViewCell.self)")
|
||||
tableView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
|
||||
return tableView
|
||||
}()
|
||||
|
||||
override func makeUI() {
|
||||
self.title = NSLocalizedString("serverList")
|
||||
|
||||
navigationItem.setRightBarButtonItem(item: UIBarButtonItem(customView: closeButton))
|
||||
|
||||
self.view.addSubview(tableView)
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
|
||||
closeButton.rx.tap.subscribe {[weak self] in
|
||||
self?.dismiss(animated: true, completion: nil)
|
||||
} onError: { _ in
|
||||
|
||||
}.disposed(by: rx.disposeBag)
|
||||
}
|
||||
|
||||
override func bindViewModel() {
|
||||
|
||||
let action = getServerAction()
|
||||
|
||||
// 复制 server
|
||||
let copyServer = action.filter { $0.1 == ServerActionType.copy }
|
||||
.map { $0.0 }.asDriver(onErrorDriveWith: .empty())
|
||||
|
||||
// 删除 server
|
||||
let deleteServer = action.filter { $0.1 == ServerActionType.delete }
|
||||
.map { $0.0 }.asDriver(onErrorDriveWith: .empty())
|
||||
|
||||
// 重置 server key
|
||||
let resetServer = action.compactMap { r -> (Server, String?)? in
|
||||
if case let ServerActionType.reset(key) = r.1 {
|
||||
return (r.0, key)
|
||||
}
|
||||
return nil
|
||||
}.asDriver(onErrorDriveWith: .empty())
|
||||
|
||||
let output = viewModel.transform(input: ServerListViewModel.Input(
|
||||
copyServer: copyServer,
|
||||
deleteServer: deleteServer,
|
||||
resetServer: resetServer
|
||||
))
|
||||
|
||||
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, ServerListTableViewCellViewModel>> { _, tableView, _, item -> UITableViewCell in
|
||||
if let cell = tableView.dequeueReusableCell(withIdentifier: "\(ServerListTableViewCell.self)") as? ServerListTableViewCell {
|
||||
cell.bindViewModel(model: item)
|
||||
return cell
|
||||
}
|
||||
return UITableViewCell()
|
||||
}
|
||||
|
||||
// TableView数据源
|
||||
output.servers
|
||||
.drive(self.tableView.rx.items(dataSource: dataSource))
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
// 复制文本
|
||||
output.copy
|
||||
.drive(onNext: { [weak self] text in
|
||||
UIPasteboard.general.string = text
|
||||
self?.showSnackbar(text: NSLocalizedString("Copy"))
|
||||
})
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
// 弹出提示
|
||||
output.showSnackbar
|
||||
.drive(onNext: { [weak self] text in
|
||||
self?.showSnackbar(text: text)
|
||||
})
|
||||
.disposed(by: rx.disposeBag)
|
||||
}
|
||||
|
||||
func getServerAction() -> Driver<(Server, ServerActionType)> {
|
||||
|
||||
return tableView.rx
|
||||
.modelSelected(ServerListTableViewCellViewModel.self)
|
||||
.flatMapLatest { viewModel -> PublishRelay<(Server, ServerActionType)> in
|
||||
let relay = PublishRelay<(Server, ServerActionType)>()
|
||||
|
||||
let alertController = UIAlertController(title: nil, message: "\(viewModel.address.value)", preferredStyle: .actionSheet)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("copyAddressAndKey"), style: .default, handler: { _ in
|
||||
relay.accept((viewModel.server, .copy))
|
||||
}))
|
||||
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("resetKey"), style: .default, handler: { _ in
|
||||
let alertController = UIAlertController(title: NSLocalizedString("resetKey"), message: NSLocalizedString("resetKeyDesc"), preferredStyle: .alert)
|
||||
alertController.addTextField { textField in
|
||||
textField.placeholder = NSLocalizedString("resetKeyPlaceholder")
|
||||
}
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("confirm"), style: .default, handler: { _ in
|
||||
relay.accept((viewModel.server, .reset(key: alertController.textFields?.first?.text)))
|
||||
}))
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
|
||||
self.navigationController?.present(alertController, animated: true, completion: nil)
|
||||
}))
|
||||
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("deleteServer"), style: .destructive, handler: { _ in
|
||||
|
||||
let alertController = UIAlertController(title: nil, message: NSLocalizedString("confirmDeleteServer"), preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("confirm"), style: .destructive, handler: { _ in
|
||||
relay.accept((viewModel.server, .delete))
|
||||
}))
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
|
||||
self.navigationController?.present(alertController, animated: true, completion: nil)
|
||||
|
||||
}))
|
||||
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
|
||||
self.navigationController?.present(alertController, animated: true, completion: nil)
|
||||
|
||||
return relay
|
||||
}.asDriver(onErrorDriveWith: .empty())
|
||||
}
|
||||
}
|
||||
158
Controller/ServerListViewModel.swift
Normal file
@ -0,0 +1,158 @@
|
||||
//
|
||||
// ServerListViewModel.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2022/3/25.
|
||||
// Copyright © 2022 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import Differentiator
|
||||
import Foundation
|
||||
import Moya
|
||||
import RxCocoa
|
||||
import RxSwift
|
||||
import SwiftyJSON
|
||||
|
||||
class ServerListViewModel: ViewModel, ViewModelType {
|
||||
struct Input {
|
||||
let copyServer: Driver<Server>
|
||||
let deleteServer: Driver<Server>
|
||||
let resetServer: Driver<(Server, String?)>
|
||||
}
|
||||
|
||||
struct Output {
|
||||
let servers: Driver<[SectionModel<String, ServerListTableViewCellViewModel>]>
|
||||
let showSnackbar: Driver<String>
|
||||
let copy: Driver<String>
|
||||
}
|
||||
|
||||
let currentServerChanged = PublishRelay<Server>()
|
||||
|
||||
func transform(input: Input) -> Output {
|
||||
// 弹出提示消息
|
||||
let showSnackbar = PublishRelay<String>()
|
||||
|
||||
// 复制 Server
|
||||
let copy = input.copyServer.map { server -> String in
|
||||
"\(server.address)/\(server.key)/"
|
||||
}
|
||||
|
||||
// 删除检查,需要至少保留一个服务器
|
||||
let deleteCheck = input.deleteServer.map { server -> Server? in
|
||||
if ServerManager.shared.servers.count > 1 {
|
||||
return server
|
||||
}
|
||||
return nil
|
||||
}.asObservable().share()
|
||||
|
||||
// 删除检查错误提示
|
||||
deleteCheck.filter { $0 == nil }
|
||||
.map { _ in NSLocalizedString("deleteFailed") }
|
||||
.bind(to: showSnackbar)
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
// 对即将删除的服务器发送错误的 deviceToken,防止服务器依然保留旧的推送链接。
|
||||
let delete = deleteCheck.compactMap { $0 }
|
||||
.flatMapLatest { server -> Observable<Result<JSON, ApiError>>in
|
||||
if server.key.count > 0 {
|
||||
return BarkApi.provider
|
||||
.request(.register(address: server.address, key: server.key, devicetoken: "deleted"))
|
||||
.filterResponseError()
|
||||
}
|
||||
return Observable.just(Result<JSON, ApiError>.success(JSON()))
|
||||
}
|
||||
|
||||
// 服务器远程注销后,再本地删除
|
||||
// withLatestFrom 将在 delete 产生新的事件时,
|
||||
// 取 input.deleteServer 最后一个事件的元素。(这里是对应的 server )
|
||||
let serverDeleted = delete.withLatestFrom(input.deleteServer)
|
||||
.map { server in
|
||||
ServerManager.shared.removeServer(server: server)
|
||||
}
|
||||
|
||||
// 弹出删除提示
|
||||
serverDeleted.map { NSLocalizedString("deletedSuccessfully") }
|
||||
.bind(to: showSnackbar)
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
// 重置服务器之前,先检查 DeviceToken
|
||||
let resetServer = input.resetServer
|
||||
.map { ($0.0, $0.1, Client.shared.deviceToken.value) }
|
||||
.asObservable()
|
||||
|
||||
// 重置检查错误提示
|
||||
resetServer.filter { ($0.2?.count ?? 0) <= 0 }
|
||||
.map { _ in NSLocalizedString("resetFailed2") }
|
||||
.bind(to: showSnackbar)
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
// 发送重置请求
|
||||
let serverReseted = resetServer.filter { ($0.2?.count ?? 0) > 0 }
|
||||
.flatMapLatest { r -> Observable<Result<JSON, ApiError>> in
|
||||
let server = r.0
|
||||
let newKey = r.1
|
||||
let deviceToken = r.2!
|
||||
return BarkApi.provider
|
||||
.request(.register(address: server.address, key: newKey, devicetoken: deviceToken))
|
||||
.filterResponseError()
|
||||
}
|
||||
.map { result -> String? in
|
||||
switch result {
|
||||
case .success(let json):
|
||||
return json["data", "key"].rawString()
|
||||
case .failure:
|
||||
return nil
|
||||
}
|
||||
}.share()
|
||||
|
||||
// 重置成功后,更新本地服务器列表
|
||||
let serverResetSuccess = serverReseted.compactMap { $0 }.withLatestFrom(input.resetServer) { newKey, r in
|
||||
let server = r.0
|
||||
server.key = newKey
|
||||
ServerManager.shared.updateServerKey(server: server)
|
||||
}
|
||||
|
||||
// 重置失败提示
|
||||
serverReseted.filter { $0 == nil }
|
||||
.map { _ in NSLocalizedString("resetFailed") }
|
||||
.bind(to: showSnackbar)
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
// 服务器列表
|
||||
let servers = Observable
|
||||
.merge(
|
||||
Observable.just(()),
|
||||
serverDeleted,
|
||||
serverResetSuccess
|
||||
)
|
||||
.map {
|
||||
[SectionModel(
|
||||
model: "servers",
|
||||
items: ServerManager.shared.servers.map { ServerListTableViewCellViewModel(server: $0) }
|
||||
)]
|
||||
}.asDriver(onErrorDriveWith: .empty())
|
||||
|
||||
// 当前服务器有改动
|
||||
let serverChanged = Observable.merge(serverDeleted, serverResetSuccess)
|
||||
.share()
|
||||
|
||||
serverChanged.map {
|
||||
ServerManager.shared.currentServer
|
||||
}
|
||||
.bind(to: self.currentServerChanged)
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
// 服务器改变时,同步 Client.shared.state。
|
||||
serverChanged.map {
|
||||
ServerManager.shared.currentServer.state
|
||||
}
|
||||
.bind(to: Client.shared.state)
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
return Output(
|
||||
servers: servers,
|
||||
showSnackbar: showSnackbar.asDriver(onErrorDriveWith: .empty()),
|
||||
copy: copy
|
||||
)
|
||||
}
|
||||
}
|
||||
117
View/ServerListTableViewCell.swift
Normal file
@ -0,0 +1,117 @@
|
||||
//
|
||||
// ServerListTableViewCell.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2022/4/1.
|
||||
// Copyright © 2022 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import Material
|
||||
import UIKit
|
||||
|
||||
class ServerListTableViewCell: BaseTableViewCell<ServerListTableViewCellViewModel> {
|
||||
|
||||
let backgroundPanel: UIView = {
|
||||
let view = UIView()
|
||||
view.layer.cornerRadius = 3
|
||||
view.clipsToBounds = true
|
||||
view.backgroundColor = BKColor.background.secondary
|
||||
view.layer.cornerRadius = 25
|
||||
view.clipsToBounds = true
|
||||
view.layer.borderColor = BKColor.grey.lighten3.cgColor
|
||||
view.layer.borderWidth = 1
|
||||
return view
|
||||
}()
|
||||
|
||||
let addressLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = RobotoFont.medium(with: 14)
|
||||
label.textColor = BKColor.grey.darken4
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
let keyLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = RobotoFont.regular(with: 12)
|
||||
label.textColor = BKColor.grey.darken4
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
let stateImageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
imageView.layer.cornerRadius = 15
|
||||
imageView.clipsToBounds = true
|
||||
return imageView
|
||||
}()
|
||||
|
||||
var state: Bool = false {
|
||||
didSet {
|
||||
if state {
|
||||
stateImageView.image = UIImage(named: "online")
|
||||
}
|
||||
else {
|
||||
stateImageView.image = UIImage(named: "offline")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
self.selectionStyle = .none
|
||||
self.backgroundColor = BKColor.background.primary
|
||||
|
||||
addSubview(backgroundPanel)
|
||||
addSubview(stateImageView)
|
||||
addSubview(addressLabel)
|
||||
addSubview(keyLabel)
|
||||
|
||||
backgroundPanel.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(18)
|
||||
make.right.equalToSuperview().offset(-18)
|
||||
make.top.equalToSuperview().offset(5)
|
||||
make.bottom.equalToSuperview().offset(-5)
|
||||
make.height.equalTo(50)
|
||||
}
|
||||
|
||||
stateImageView.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(backgroundPanel)
|
||||
make.left.equalTo(backgroundPanel).offset(13)
|
||||
make.width.height.equalTo(30)
|
||||
}
|
||||
addressLabel.snp.makeConstraints { make in
|
||||
make.left.equalTo(stateImageView.snp.right).offset(8)
|
||||
make.top.equalTo(backgroundPanel).offset(8)
|
||||
make.right.equalTo(backgroundPanel).offset(-8)
|
||||
}
|
||||
keyLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(addressLabel.snp.bottom).offset(1)
|
||||
make.left.right.equalTo(addressLabel)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func bindViewModel(model: ServerListTableViewCellViewModel) {
|
||||
super.bindViewModel(model: model)
|
||||
|
||||
model.address
|
||||
.bind(to: addressLabel.rx.text)
|
||||
.disposed(by: rx.reuseBag)
|
||||
|
||||
model.key
|
||||
.bind(to: keyLabel.rx.text)
|
||||
.disposed(by: rx.reuseBag)
|
||||
|
||||
model.state
|
||||
.subscribe { state in
|
||||
self.state = state
|
||||
} onError: { _ in }
|
||||
.disposed(by: rx.reuseBag)
|
||||
}
|
||||
}
|
||||
30
View/ServerListTableViewCellViewModel.swift
Normal file
@ -0,0 +1,30 @@
|
||||
//
|
||||
// ServerListTableViewCellViewModel.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2022/4/1.
|
||||
// Copyright © 2022 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import RxRelay
|
||||
import UIKit
|
||||
|
||||
class ServerListTableViewCellViewModel: ViewModel {
|
||||
let server: Server
|
||||
|
||||
let address: BehaviorRelay<String>
|
||||
let key: BehaviorRelay<String>
|
||||
let state: BehaviorRelay<Bool>
|
||||
|
||||
init(server: Server) {
|
||||
self.server = server
|
||||
|
||||
self.address = BehaviorRelay<String>(value: {
|
||||
URL(string: server.address)?.host ?? "Invalid Server"
|
||||
}())
|
||||
self.key = BehaviorRelay<String>(value: !server.key.isEmpty ? server.key : "none")
|
||||
self.state = BehaviorRelay<Bool>(value: server.state == .ok)
|
||||
|
||||
super.init()
|
||||
}
|
||||
}
|
||||