mirror of
https://github.com/Finb/Bark.git
synced 2025-12-08 21:36:01 +00:00
Compare commits
8 Commits
bfed640e35
...
7a9a70eb25
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a9a70eb25 | ||
|
|
355e2e7cb0 | ||
|
|
2bc0eb33b8 | ||
|
|
786e5f2232 | ||
|
|
13b1a198d1 | ||
|
|
accf1008e7 | ||
|
|
f56bfe82b6 | ||
|
|
c1a9e0aa78 |
2
.github/workflows/testflight.yaml
vendored
2
.github/workflows/testflight.yaml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
- name: Select Xcode Version
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: 'latest-stable'
|
||||
xcode-version: '16.1'
|
||||
|
||||
- name: Setup ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
|
||||
@ -118,10 +118,6 @@
|
||||
068EC15A27ED99E700D5D11E /* ServerListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068EC15927ED99E700D5D11E /* ServerListViewModel.swift */; };
|
||||
068F66B3247BD84C00DAD25A /* MessageListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068F66B2247BD84C00DAD25A /* MessageListViewController.swift */; };
|
||||
0699473D2D223094008D5E40 /* CustomTapTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0699473C2D223094008D5E40 /* CustomTapTextView.swift */; };
|
||||
06AE3118266F4E2E00B39FBB /* GroupFilterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AE3117266F4E2E00B39FBB /* GroupFilterViewController.swift */; };
|
||||
06AE311A266F4E6600B39FBB /* GroupFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AE3119266F4E6600B39FBB /* GroupFilterViewModel.swift */; };
|
||||
06AE311C266F54A500B39FBB /* GroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AE311B266F54A500B39FBB /* GroupTableViewCell.swift */; };
|
||||
06AE311E266F54CC00B39FBB /* GroupCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AE311D266F54CC00B39FBB /* GroupCellViewModel.swift */; };
|
||||
06B1158D247BA6D5006D91FB /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06B1158C247BA6D5006D91FB /* CloudKit.framework */; };
|
||||
06B1158F247BB1FB006D91FB /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06B1158E247BB1FB006D91FB /* Message.swift */; };
|
||||
06B11591247BC132006D91FB /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06B1158E247BB1FB006D91FB /* Message.swift */; };
|
||||
@ -179,6 +175,7 @@
|
||||
06D69E3E2C1159E200161A35 /* alarm.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204F3250B6DD1001561EC /* alarm.caf */; };
|
||||
06D69E3F2C1159E200161A35 /* fanfare.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204FB250B6DD2001561EC /* fanfare.caf */; };
|
||||
06D69E412C11983E00161A35 /* CallProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D69E402C11983E00161A35 /* CallProcessor.swift */; };
|
||||
06E62C112D670F62004DC82B /* PushToCurrentIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E62C102D670F5D004DC82B /* PushToCurrentIntent.swift */; };
|
||||
06E944682C06E40600AC86AB /* NotificationContentProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E944672C06E40600AC86AB /* NotificationContentProcessor.swift */; };
|
||||
06E9446A2C06E4A200AC86AB /* CiphertextProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E944692C06E4A200AC86AB /* CiphertextProcessor.swift */; };
|
||||
06E9446D2C06FEC900AC86AB /* LevelProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E9446C2C06FEC900AC86AB /* LevelProcessor.swift */; };
|
||||
@ -193,6 +190,9 @@
|
||||
06EE1FD326843E9300586708 /* BarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EE1FD226843E9300586708 /* BarkTests.swift */; };
|
||||
06EEF333291CCFF400CA228A /* CryptoSettingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EEF332291CCFF400CA228A /* CryptoSettingController.swift */; };
|
||||
06EEF335291CD00000CA228A /* CryptoSettingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EEF334291CD00000CA228A /* CryptoSettingViewModel.swift */; };
|
||||
06EF49152D682A99008B91D2 /* PushResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EF49142D682A96008B91D2 /* PushResponse.swift */; };
|
||||
06EF49172D682AC4008B91D2 /* OptionsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EF49162D682AC3008B91D2 /* OptionsProvider.swift */; };
|
||||
06EF49192D682B3B008B91D2 /* PushToOtherIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EF49182D682B34008B91D2 /* PushToOtherIntent.swift */; };
|
||||
06F08EA429B098DD006AB9CA /* CryptoSettingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EA329B098DD006AB9CA /* CryptoSettingManager.swift */; };
|
||||
06F08EA529B1DDA7006AB9CA /* Algorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 061894C629A75BEA00E001C2 /* Algorithm.swift */; };
|
||||
06F08EA729B1DDFE006AB9CA /* Error+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EA629B1DDFE006AB9CA /* Error+Extension.swift */; };
|
||||
@ -363,10 +363,6 @@
|
||||
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>"; };
|
||||
0699473C2D223094008D5E40 /* CustomTapTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTapTextView.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>"; };
|
||||
06AE311B266F54A500B39FBB /* GroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupTableViewCell.swift; sourceTree = "<group>"; };
|
||||
06AE311D266F54CC00B39FBB /* GroupCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupCellViewModel.swift; sourceTree = "<group>"; };
|
||||
06B1158C247BA6D5006D91FB /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
|
||||
06B1158E247BB1FB006D91FB /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = "<group>"; };
|
||||
06B11590247BBC15006D91FB /* NotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationServiceExtension.entitlements; sourceTree = "<group>"; };
|
||||
@ -391,6 +387,7 @@
|
||||
06CF784421C7A50300A052D7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
06CF784B21C7A51200A052D7 /* NotificationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
06D69E402C11983E00161A35 /* CallProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallProcessor.swift; sourceTree = "<group>"; };
|
||||
06E62C102D670F5D004DC82B /* PushToCurrentIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushToCurrentIntent.swift; sourceTree = "<group>"; };
|
||||
06E944672C06E40600AC86AB /* NotificationContentProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentProcessor.swift; sourceTree = "<group>"; };
|
||||
06E944692C06E4A200AC86AB /* CiphertextProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CiphertextProcessor.swift; sourceTree = "<group>"; };
|
||||
06E9446C2C06FEC900AC86AB /* LevelProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LevelProcessor.swift; sourceTree = "<group>"; };
|
||||
@ -406,6 +403,9 @@
|
||||
06EE1FD426843E9300586708 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
06EEF332291CCFF400CA228A /* CryptoSettingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSettingController.swift; sourceTree = "<group>"; };
|
||||
06EEF334291CD00000CA228A /* CryptoSettingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSettingViewModel.swift; sourceTree = "<group>"; };
|
||||
06EF49142D682A96008B91D2 /* PushResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushResponse.swift; sourceTree = "<group>"; };
|
||||
06EF49162D682AC3008B91D2 /* OptionsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionsProvider.swift; sourceTree = "<group>"; };
|
||||
06EF49182D682B34008B91D2 /* PushToOtherIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushToOtherIntent.swift; sourceTree = "<group>"; };
|
||||
06F08EA329B098DD006AB9CA /* CryptoSettingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSettingManager.swift; sourceTree = "<group>"; };
|
||||
06F08EA629B1DDFE006AB9CA /* Error+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Error+Extension.swift"; sourceTree = "<group>"; };
|
||||
06F08EAB29B1DECD006AB9CA /* NSLocalizedString+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLocalizedString+Extension.swift"; sourceTree = "<group>"; };
|
||||
@ -484,8 +484,6 @@
|
||||
06BBB8B62567AC140076F63E /* MessageSettingsViewModel.swift */,
|
||||
060481ED250F404500BC9799 /* SoundsViewController.swift */,
|
||||
065BE43F2563D649002A8CA4 /* SoundsViewModel.swift */,
|
||||
06AE3117266F4E2E00B39FBB /* GroupFilterViewController.swift */,
|
||||
06AE3119266F4E6600B39FBB /* GroupFilterViewModel.swift */,
|
||||
06F11E7627D9D5FB00F00298 /* QRScannerViewController.swift */,
|
||||
068EC15727ED99C900D5D11E /* ServerListViewController.swift */,
|
||||
068EC15927ED99E700D5D11E /* ServerListViewModel.swift */,
|
||||
@ -517,8 +515,6 @@
|
||||
062B98C2251B2762004562E7 /* BKButton.swift */,
|
||||
06C595352481160F006B98F3 /* BKLabel.swift */,
|
||||
062B98C7251B27AE004562E7 /* UINavigationItem+Extension.swift */,
|
||||
06AE311B266F54A500B39FBB /* GroupTableViewCell.swift */,
|
||||
06AE311D266F54CC00B39FBB /* GroupCellViewModel.swift */,
|
||||
06C2CF222685B88D0034B127 /* TextCell.swift */,
|
||||
06C2CF242685BDB80034B127 /* SpacerCell.swift */,
|
||||
0642B55927EB13F100453D91 /* MutableTextCell.swift */,
|
||||
@ -675,6 +671,7 @@
|
||||
0661A541204FDA4100965E4E /* Bark */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
06EF49132D682A82008B91D2 /* Intents */,
|
||||
0683486A2050F1310024B6DA /* Bark.entitlements */,
|
||||
0661A542204FDA4100965E4E /* AppDelegate.swift */,
|
||||
0661A549204FDA4100965E4E /* Assets.xcassets */,
|
||||
@ -738,6 +735,17 @@
|
||||
path = BarkTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
06EF49132D682A82008B91D2 /* Intents */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
06EF49162D682AC3008B91D2 /* OptionsProvider.swift */,
|
||||
06EF49142D682A96008B91D2 /* PushResponse.swift */,
|
||||
06E62C102D670F5D004DC82B /* PushToCurrentIntent.swift */,
|
||||
06EF49182D682B34008B91D2 /* PushToOtherIntent.swift */,
|
||||
);
|
||||
path = Intents;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
99BD309BDB7F62B5DC0CECE1 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1236,9 +1244,9 @@
|
||||
0603706920E1F89500F4CA05 /* PreviewCardCell.swift in Sources */,
|
||||
066890082D1946D500E106F2 /* MessageItemView.swift in Sources */,
|
||||
0627DABB298B6EA2002F3F69 /* DropBoxView.swift in Sources */,
|
||||
06AE311C266F54A500B39FBB /* GroupTableViewCell.swift in Sources */,
|
||||
0672CB06256903F700570C9D /* MessageListViewModel.swift in Sources */,
|
||||
0627DABD2990D615002F3F69 /* BorderTextField.swift in Sources */,
|
||||
06E62C112D670F62004DC82B /* PushToCurrentIntent.swift in Sources */,
|
||||
0633E80A256A091B00ED0680 /* MJRefresh+Rx.swift in Sources */,
|
||||
0637FA8C20E0D7A700E80174 /* BaseViewController.swift in Sources */,
|
||||
0642B55C27EB149900453D91 /* MutableTextCellViewModel.swift in Sources */,
|
||||
@ -1247,7 +1255,6 @@
|
||||
0699473D2D223094008D5E40 /* CustomTapTextView.swift in Sources */,
|
||||
06EEF333291CCFF400CA228A /* CryptoSettingController.swift in Sources */,
|
||||
0603706D20E23EC000F4CA05 /* BarkSFSafariViewController.swift in Sources */,
|
||||
06AE311A266F4E6600B39FBB /* GroupFilterViewModel.swift in Sources */,
|
||||
06F08EA429B098DD006AB9CA /* CryptoSettingManager.swift in Sources */,
|
||||
0668900B2D19525400E106F2 /* ShowLessAndClearView.swift in Sources */,
|
||||
06BD4DAA2901352E003364DB /* Object+Dictionary.swift in Sources */,
|
||||
@ -1274,18 +1281,17 @@
|
||||
06C5952F248107F5006B98F3 /* iCloudStatusCell.swift in Sources */,
|
||||
06BBB88725650C6C0076F63E /* ArchiveSettingManager.swift in Sources */,
|
||||
065BE44B2563D8E1002A8CA4 /* Reusable.swift in Sources */,
|
||||
06AE311E266F54CC00B39FBB /* GroupCellViewModel.swift in Sources */,
|
||||
0637FA8620E0AB6600E80174 /* UIColor+Extension.swift in Sources */,
|
||||
0637FA8A20E0D58800E80174 /* NewServerViewController.swift in Sources */,
|
||||
067B2EB525693E38008B6BE1 /* MessageSection.swift in Sources */,
|
||||
0637FA8220E09C4B00E80174 /* BarkNavigationController.swift in Sources */,
|
||||
06AE3118266F4E2E00B39FBB /* GroupFilterViewController.swift in Sources */,
|
||||
0637FA7A20E092B300E80174 /* Observable+Extension.swift in Sources */,
|
||||
060481F0250F51CA00BC9799 /* SoundCell.swift in Sources */,
|
||||
0667D194247D1BA0005DE2ED /* Date+Extension.swift in Sources */,
|
||||
0604F7DF20620D4900B32F09 /* ServerManager.swift in Sources */,
|
||||
0667D192247D162C005DE2ED /* MessageTableViewCell.swift in Sources */,
|
||||
06E944762C07013000AC86AB /* RealmConfiguration.swift in Sources */,
|
||||
06EF49152D682A99008B91D2 /* PushResponse.swift in Sources */,
|
||||
0603706720E1E31600F4CA05 /* Defines.swift in Sources */,
|
||||
06787C3B2AB82BDB008ABDD7 /* CrashReportViewController.swift in Sources */,
|
||||
064CAB9E256BE9090018155C /* PreviewCardCellViewModel.swift in Sources */,
|
||||
@ -1308,6 +1314,7 @@
|
||||
06EEF335291CD00000CA228A /* CryptoSettingViewModel.swift in Sources */,
|
||||
0637FA8020E0981E00E80174 /* BarkSettings.swift in Sources */,
|
||||
065BE4402563D649002A8CA4 /* SoundsViewModel.swift in Sources */,
|
||||
06EF49192D682B3B008B91D2 /* PushToOtherIntent.swift in Sources */,
|
||||
0647DB682CE604AA00102066 /* MessageSettingFooter.swift in Sources */,
|
||||
065BE4462563D7E5002A8CA4 /* ViewModelType.swift in Sources */,
|
||||
0603706B20E20A7C00F4CA05 /* String+Extension.swift in Sources */,
|
||||
@ -1318,6 +1325,7 @@
|
||||
06BBB8B72567AC140076F63E /* MessageSettingsViewModel.swift in Sources */,
|
||||
06BBB8C12567B3EF0076F63E /* BaseTableViewCell.swift in Sources */,
|
||||
0661A543204FDA4100965E4E /* AppDelegate.swift in Sources */,
|
||||
06EF49172D682AC4008B91D2 /* OptionsProvider.swift in Sources */,
|
||||
06F08EAF29B5D9FF006AB9CA /* HUD.swift in Sources */,
|
||||
065AE76B2987777F00323230 /* ArchiveSettingRelay.swift in Sources */,
|
||||
06F08EAC29B1DECD006AB9CA /* NSLocalizedString+Extension.swift in Sources */,
|
||||
|
||||
64
Bark/Intents/OptionsProvider.swift
Normal file
64
Bark/Intents/OptionsProvider.swift
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// OptionsProvider.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2/21/25.
|
||||
// Copyright © 2025 Fin. All rights reserved.
|
||||
//
|
||||
import AppIntents
|
||||
|
||||
@available(iOS 16, *)
|
||||
struct ServerAddressOptionsProvider: DynamicOptionsProvider {
|
||||
func results() async throws -> [String] {
|
||||
return ServerManager.shared.servers.map { server in
|
||||
return server.address + "/" + server.key
|
||||
}
|
||||
}
|
||||
|
||||
func defaultResult() async -> String? {
|
||||
return ServerManager.shared.currentServer.address + "/" + ServerManager.shared.currentServer.key
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16, *)
|
||||
struct SoundOptionsProvider: DynamicOptionsProvider {
|
||||
func results() async throws -> [String] {
|
||||
var customSounds: [String] = []
|
||||
if let soundsDirectoryUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bark")?.appendingPathComponent("Library/Sounds").path {
|
||||
customSounds = getSounds(urls: getFilesInDirectory(directory: soundsDirectoryUrl, suffix: "caf"))
|
||||
}
|
||||
let defaultSounds = getSounds(urls: Bundle.main.urls(forResourcesWithExtension: "caf", subdirectory: nil) ?? [])
|
||||
|
||||
return customSounds + defaultSounds
|
||||
}
|
||||
|
||||
func getSounds(urls: [URL]) -> [String] {
|
||||
let urls = urls.sorted { u1, u2 -> Bool in
|
||||
u1.lastPathComponent.localizedStandardCompare(u2.lastPathComponent) == ComparisonResult.orderedAscending
|
||||
}
|
||||
return urls.map { $0.deletingPathExtension().lastPathComponent }
|
||||
}
|
||||
|
||||
func getFilesInDirectory(directory: String, suffix: String) -> [URL] {
|
||||
let fileManager = FileManager.default
|
||||
do {
|
||||
let files = try fileManager.contentsOfDirectory(atPath: directory)
|
||||
return files.compactMap { file -> URL? in
|
||||
if file.hasSuffix(suffix), !file.hasPrefix(kBarkSoundPrefix) {
|
||||
// 不要包含 kBarkSoundPrefix 开头的,这些是为了 call=1 合成的 30s 长铃声,不算用户上传的
|
||||
return URL(fileURLWithPath: directory).appendingPathComponent(file)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16, *)
|
||||
struct VolumeOptionsProvider: DynamicOptionsProvider {
|
||||
func results() async throws -> [Int] {
|
||||
return Array(0...10)
|
||||
}
|
||||
}
|
||||
12
Bark/Intents/PushResponse.swift
Normal file
12
Bark/Intents/PushResponse.swift
Normal file
@ -0,0 +1,12 @@
|
||||
//
|
||||
// PushResponse.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2/21/25.
|
||||
// Copyright © 2025 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
struct PushResponse: Decodable {
|
||||
let message: String?
|
||||
let code: Int?
|
||||
}
|
||||
88
Bark/Intents/PushToCurrentIntent.swift
Normal file
88
Bark/Intents/PushToCurrentIntent.swift
Normal file
@ -0,0 +1,88 @@
|
||||
//
|
||||
// Intents.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2/20/25.
|
||||
// Copyright © 2025 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import Alamofire
|
||||
import AppIntents
|
||||
|
||||
@available(iOS 16, *)
|
||||
struct PushToCurrentIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "sendPushNotification"
|
||||
static var openAppWhenRun: Bool = false
|
||||
|
||||
@Parameter(title: "ServerAddress", optionsProvider: ServerAddressOptionsProvider())
|
||||
var address: String
|
||||
|
||||
@Parameter(title: "CustomedNotificationTitle")
|
||||
var title: String?
|
||||
@Parameter(title: "CustomedNotificationContent")
|
||||
var body: String?
|
||||
|
||||
@Parameter(title: "ringtone")
|
||||
var isCall: Bool
|
||||
|
||||
@Parameter(title: "criticalAlert")
|
||||
var isCritical: Bool
|
||||
|
||||
@Parameter(title: "ringtoneVolume", optionsProvider: VolumeOptionsProvider())
|
||||
var volume: Int?
|
||||
|
||||
@Parameter(title: "notificationSound", optionsProvider: SoundOptionsProvider())
|
||||
var sound: String?
|
||||
|
||||
@Parameter(title: "notificationIcon")
|
||||
var icon: URL?
|
||||
|
||||
@Parameter(title: "group")
|
||||
var group: String?
|
||||
|
||||
func perform() async throws -> some IntentResult & ReturnsValue<Bool> {
|
||||
guard let address = URL(string: address) else {
|
||||
throw "Invalid URL"
|
||||
}
|
||||
|
||||
var params: [String: Any] = [:]
|
||||
|
||||
if let title, !title.isEmpty {
|
||||
params["title"] = title.urlDecoded()
|
||||
}
|
||||
if let body, !body.isEmpty {
|
||||
params["body"] = body.urlDecoded()
|
||||
}
|
||||
if title == nil, body == nil {
|
||||
params["body"] = "Empty Notification"
|
||||
}
|
||||
if isCritical {
|
||||
params["level"] = "critical"
|
||||
}
|
||||
if let volume {
|
||||
params["volume"] = volume
|
||||
}
|
||||
if isCall {
|
||||
params["call"] = 1
|
||||
}
|
||||
if let sound, !sound.isEmpty {
|
||||
params["sound"] = sound
|
||||
}
|
||||
if let icon {
|
||||
params["icon"] = icon.absoluteString
|
||||
}
|
||||
if let group, !group.isEmpty {
|
||||
params["group"] = group
|
||||
}
|
||||
|
||||
let response = await AF.request(address, method: .post, parameters: params, encoding: JSONEncoding.default)
|
||||
.serializingDecodable(PushResponse.self)
|
||||
.response
|
||||
|
||||
// 打印返回的body
|
||||
if response.response?.statusCode != 200 {
|
||||
return .result(value: false)
|
||||
}
|
||||
return .result(value: true)
|
||||
}
|
||||
}
|
||||
87
Bark/Intents/PushToOtherIntent.swift
Normal file
87
Bark/Intents/PushToOtherIntent.swift
Normal file
@ -0,0 +1,87 @@
|
||||
//
|
||||
// PushToOtherIntent.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2/21/25.
|
||||
// Copyright © 2025 Fin. All rights reserved.
|
||||
//
|
||||
import Alamofire
|
||||
import AppIntents
|
||||
|
||||
@available(iOS 16, *)
|
||||
struct PushToOtherIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "sendPushNotificationToOther"
|
||||
static var openAppWhenRun: Bool = false
|
||||
|
||||
@Parameter(title: "ServerAddress")
|
||||
var address: String
|
||||
|
||||
@Parameter(title: "CustomedNotificationTitle")
|
||||
var title: String?
|
||||
@Parameter(title: "CustomedNotificationContent")
|
||||
var body: String?
|
||||
|
||||
@Parameter(title: "ringtone")
|
||||
var isCall: Bool
|
||||
|
||||
@Parameter(title: "criticalAlert")
|
||||
var isCritical: Bool
|
||||
|
||||
@Parameter(title: "ringtoneVolume", optionsProvider: VolumeOptionsProvider())
|
||||
var volume: Int?
|
||||
|
||||
@Parameter(title: "notificationSound", optionsProvider: SoundOptionsProvider())
|
||||
var sound: String?
|
||||
|
||||
@Parameter(title: "notificationIcon")
|
||||
var icon: URL?
|
||||
|
||||
@Parameter(title: "group")
|
||||
var group: String?
|
||||
|
||||
func perform() async throws -> some IntentResult & ReturnsValue<Bool> {
|
||||
guard let address = URL(string: address) else {
|
||||
throw "Invalid URL"
|
||||
}
|
||||
|
||||
var params: [String: Any] = [:]
|
||||
|
||||
if let title, !title.isEmpty {
|
||||
params["title"] = title.urlDecoded()
|
||||
}
|
||||
if let body, !body.isEmpty {
|
||||
params["body"] = body.urlDecoded()
|
||||
}
|
||||
if title == nil, body == nil {
|
||||
params["body"] = "Empty Notification"
|
||||
}
|
||||
if isCritical {
|
||||
params["level"] = "critical"
|
||||
}
|
||||
if let volume {
|
||||
params["volume"] = volume
|
||||
}
|
||||
if isCall {
|
||||
params["call"] = 1
|
||||
}
|
||||
if let sound, !sound.isEmpty {
|
||||
params["sound"] = sound
|
||||
}
|
||||
if let icon {
|
||||
params["icon"] = icon.absoluteString
|
||||
}
|
||||
if let group, !group.isEmpty {
|
||||
params["group"] = group
|
||||
}
|
||||
|
||||
let response = await AF.request(address, method: .post, parameters: params, encoding: JSONEncoding.default)
|
||||
.serializingDecodable(PushResponse.self)
|
||||
.response
|
||||
|
||||
// 打印返回的body
|
||||
if response.response?.statusCode != 200 {
|
||||
return .result(value: false)
|
||||
}
|
||||
return .result(value: true)
|
||||
}
|
||||
}
|
||||
@ -1920,7 +1920,7 @@
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Groups"
|
||||
"value" : "Group"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
@ -3306,6 +3306,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ringtoneVolume" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ringtone Volume for Critical Alerts"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "重要な警告の着信音量"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Önemli Uyarılar için Zil Sesi Seviyesi"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "重要警告铃声音量"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"SecureConnection" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@ -3335,6 +3364,62 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sendPushNotification" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Send notification to this device"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "このデバイスにプッシュ通知を送信"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Bu cihaza bir bildirim gönder"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "发送推送到此设备"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sendPushNotificationToOther" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Send notification to other device"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "他のデバイスにプッシュ通知を送信する"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Diğer cihazlara bildirim gönder"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "发送推送到其他设备"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ServerAddress" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@ -3509,6 +3594,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"setServerName" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Set Server Name"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "サーバー名を設定"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Sunucu Adını Ayarla"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "设置服务器名称"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"setSounds" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
|
||||
@ -17,6 +17,7 @@ class Server: Codable {
|
||||
let address: String
|
||||
var key: String
|
||||
var state: Client.ClienState
|
||||
var name: String?
|
||||
|
||||
var host: String {
|
||||
return URL(string: address)?.host ?? ""
|
||||
@ -33,6 +34,7 @@ class Server: Codable {
|
||||
case id
|
||||
case address
|
||||
case key
|
||||
case name
|
||||
}
|
||||
|
||||
// 解码
|
||||
@ -41,6 +43,7 @@ class Server: Codable {
|
||||
id = try container.decode(String.self, forKey: .id)
|
||||
address = try container.decode(String.self, forKey: .address)
|
||||
key = try container.decode(String.self, forKey: .key)
|
||||
name = try? container.decode(String?.self, forKey: .name)
|
||||
state = .ok
|
||||
}
|
||||
}
|
||||
@ -182,4 +185,9 @@ class ServerManager: NSObject {
|
||||
self.saveServers()
|
||||
}
|
||||
}
|
||||
|
||||
func setServerName(server: Server, name: String?) {
|
||||
server.name = name
|
||||
saveServers()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,101 +0,0 @@
|
||||
//
|
||||
// GroupFilterViewController.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2021/6/8.
|
||||
// Copyright © 2021 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import Material
|
||||
import MJRefresh
|
||||
import RealmSwift
|
||||
import RxCocoa
|
||||
import RxDataSources
|
||||
import RxSwift
|
||||
import UIKit
|
||||
|
||||
class GroupFilterViewController: BaseViewController<GroupFilterViewModel> {
|
||||
let doneButton: BKButton = {
|
||||
let btn = BKButton()
|
||||
btn.setTitle(NSLocalizedString("done"), for: .normal)
|
||||
btn.setTitleColor(BKColor.lightBlue.darken3, for: .normal)
|
||||
btn.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
|
||||
btn.fontSize = 14
|
||||
return btn
|
||||
}()
|
||||
|
||||
let showAllGroupsButton: BKButton = {
|
||||
let btn = BKButton()
|
||||
btn.setTitle(NSLocalizedString("hideAllGroups"), for: .selected)
|
||||
btn.setTitle(NSLocalizedString("showAllGroups"), for: .normal)
|
||||
btn.setTitleColor(Color.lightBlue.darken3, for: .normal)
|
||||
btn.fontSize = 14
|
||||
return btn
|
||||
}()
|
||||
|
||||
let tableView: UITableView = {
|
||||
let tableView = UITableView(frame: CGRect.zero, style: .insetGrouped)
|
||||
tableView.separatorStyle = .singleLine
|
||||
tableView.separatorColor = BKColor.grey.lighten3
|
||||
tableView.backgroundColor = BKColor.background.primary
|
||||
tableView.register(GroupTableViewCell.self, forCellReuseIdentifier: "\(GroupTableViewCell.self)")
|
||||
return tableView
|
||||
}()
|
||||
|
||||
override func makeUI() {
|
||||
self.title = NSLocalizedString("group")
|
||||
self.navigationItem.setRightBarButtonItem(item: UIBarButtonItem(customView: doneButton))
|
||||
|
||||
self.view.addSubview(tableView)
|
||||
self.view.addSubview(showAllGroupsButton)
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview()
|
||||
make.bottom.equalToSuperview().offset((kSafeAreaInsets.bottom + 40) * -1)
|
||||
make.left.right.equalToSuperview()
|
||||
}
|
||||
showAllGroupsButton.snp.makeConstraints { make in
|
||||
make.centerX.equalToSuperview()
|
||||
make.height.equalTo(40)
|
||||
make.bottom.equalToSuperview().offset(-kSafeAreaInsets.bottom)
|
||||
}
|
||||
|
||||
self.tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 20))
|
||||
}
|
||||
|
||||
override func bindViewModel() {
|
||||
let output = viewModel.transform(
|
||||
input: GroupFilterViewModel.Input(
|
||||
showAllGroups: self.showAllGroupsButton.rx
|
||||
.tap
|
||||
.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>> { _, tableView, _, item -> UITableViewCell in
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(GroupTableViewCell.self)") as? GroupTableViewCell else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
cell.bindViewModel(model: item)
|
||||
return cell
|
||||
}
|
||||
|
||||
output.groups
|
||||
.drive(tableView.rx.items(dataSource: dataSource))
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
output.isShowAllGroups
|
||||
.drive(self.showAllGroupsButton.rx.isSelected)
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
output.dismiss.drive(onNext: { [weak self] in
|
||||
self?.dismiss(animated: true, completion: nil)
|
||||
})
|
||||
.disposed(by: rx.disposeBag)
|
||||
}
|
||||
}
|
||||
|
||||
extension GroupFilterViewController: UITableViewDelegate {}
|
||||
@ -1,91 +0,0 @@
|
||||
//
|
||||
// GroupFilterViewModel.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2021/6/8.
|
||||
// Copyright © 2021 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import RealmSwift
|
||||
import RxCocoa
|
||||
import RxDataSources
|
||||
import RxSwift
|
||||
|
||||
struct GroupFilterModel {
|
||||
var name: String?
|
||||
var checked: Bool
|
||||
}
|
||||
|
||||
class GroupFilterViewModel: ViewModel, ViewModelType {
|
||||
let groups: [GroupFilterModel]
|
||||
init(groups: [GroupFilterModel]) {
|
||||
self.groups = groups
|
||||
}
|
||||
|
||||
struct Input {
|
||||
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 done = PublishRelay<[String?]>()
|
||||
|
||||
func transform(input: Input) -> Output {
|
||||
// 页面中的群组cellModel
|
||||
let groupCellModels = self.groups.map { filterModel in
|
||||
GroupCellViewModel(groupFilterModel: filterModel)
|
||||
}
|
||||
|
||||
// 点击显示所有群组或隐藏所有群组时,设置cell checked 勾选状态
|
||||
input.showAllGroups.drive(onNext: { isShowAllGroups in
|
||||
for model in groupCellModels {
|
||||
model.checked.accept(isShowAllGroups)
|
||||
}
|
||||
}).disposed(by: rx.disposeBag)
|
||||
|
||||
// cell checked 状态改变
|
||||
let checkChanged = Observable.merge(groupCellModels.map { model in
|
||||
model.checked.asObservable()
|
||||
})
|
||||
|
||||
// 是否勾选了所有群组
|
||||
let isShowAllGroups =
|
||||
checkChanged
|
||||
.map { _ in
|
||||
groupCellModels.filter { viewModel in
|
||||
viewModel.checked.value
|
||||
}.count >= groupCellModels.count
|
||||
}
|
||||
input.doneTap.map { () -> [String?] in
|
||||
let isShowAllGroups = groupCellModels.filter { viewModel in
|
||||
viewModel.checked.value
|
||||
}.count >= groupCellModels.count
|
||||
if isShowAllGroups {
|
||||
return []
|
||||
}
|
||||
return groupCellModels
|
||||
.filter { $0.checked.value }
|
||||
.map { $0.name.value }
|
||||
}
|
||||
.asObservable()
|
||||
.bind(to: self.done)
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
let dismiss = PublishRelay<Void>()
|
||||
input.doneTap.map { _ in () }
|
||||
.asObservable()
|
||||
.bind(to: dismiss)
|
||||
.disposed(by: rx.disposeBag)
|
||||
|
||||
return Output(
|
||||
groups: Driver.just([SectionModel(model: "header", items: groupCellModels)]),
|
||||
isShowAllGroups: isShowAllGroups.asDriver(onErrorDriveWith: .empty()),
|
||||
dismiss: dismiss.asDriver(onErrorDriveWith: .empty())
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,7 @@ enum ServerActionType {
|
||||
case copy
|
||||
case reset(key: String?)
|
||||
case delete
|
||||
case setName(name: String?)
|
||||
}
|
||||
|
||||
func == (lhs: ServerActionType, rhs: ServerActionType) -> Bool {
|
||||
@ -90,12 +91,21 @@ class ServerListViewController: BaseViewController<ServerListViewModel> {
|
||||
}
|
||||
return nil
|
||||
}.asDriver(onErrorDriveWith: .empty())
|
||||
|
||||
// 设置服务器名称
|
||||
let setServerName = action.compactMap { r -> (Server, String?)? in
|
||||
if case ServerActionType.setName(let name) = r.1 {
|
||||
return (r.0, name)
|
||||
}
|
||||
return nil
|
||||
}.asDriver(onErrorDriveWith: .empty())
|
||||
|
||||
let output = viewModel.transform(input: ServerListViewModel.Input(
|
||||
selectServer: selectServer,
|
||||
copyServer: copyServer,
|
||||
deleteServer: deleteServer,
|
||||
resetServer: resetServer
|
||||
resetServer: resetServer,
|
||||
setServerName: setServerName
|
||||
))
|
||||
|
||||
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, ServerListTableViewCellViewModel>> { _, tableView, _, item -> UITableViewCell in
|
||||
@ -136,7 +146,7 @@ class ServerListViewController: BaseViewController<ServerListViewModel> {
|
||||
return relay
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: nil, message: "\(viewModel.address.value)", preferredStyle: .actionSheet)
|
||||
let alertController = UIAlertController(title: nil, message: "\(URL(string: viewModel.server.address)?.host ?? "")", preferredStyle: .actionSheet)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("copyAddressAndKey"), style: .default, handler: { _ in
|
||||
relay.accept((viewModel.server, .copy))
|
||||
}))
|
||||
@ -157,6 +167,18 @@ class ServerListViewController: BaseViewController<ServerListViewModel> {
|
||||
relay.accept((viewModel.server, .select))
|
||||
}))
|
||||
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("setServerName"), style: .default, handler: { _ in
|
||||
let alertController = UIAlertController(title: NSLocalizedString("setServerName"), message: nil, preferredStyle: .alert)
|
||||
alertController.addTextField { textField in
|
||||
textField.text = viewModel.server.name
|
||||
}
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("confirm"), style: .default, handler: { _ in
|
||||
relay.accept((viewModel.server, .setName(name: 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)
|
||||
|
||||
@ -19,6 +19,7 @@ class ServerListViewModel: ViewModel, ViewModelType {
|
||||
let copyServer: Driver<Server>
|
||||
let deleteServer: Driver<Server>
|
||||
let resetServer: Driver<(Server, String?)>
|
||||
let setServerName: Driver<(Server, String?)>
|
||||
}
|
||||
|
||||
struct Output {
|
||||
@ -37,6 +38,11 @@ class ServerListViewModel: ViewModel, ViewModelType {
|
||||
let copy = input.copyServer.map { server -> String in
|
||||
"\(server.address)/\(server.key)/"
|
||||
}
|
||||
|
||||
// 设置服务器名称
|
||||
input.setServerName.drive(onNext: { server, name in
|
||||
ServerManager.shared.setServerName(server: server, name: name)
|
||||
}).disposed(by: rx.disposeBag)
|
||||
|
||||
// 删除检查,需要至少保留一个服务器
|
||||
let deleteCheck = input.deleteServer.map { server -> Server? in
|
||||
@ -134,7 +140,8 @@ class ServerListViewModel: ViewModel, ViewModelType {
|
||||
.merge(
|
||||
Observable.just(()),
|
||||
serverDeleted,
|
||||
serverResetSuccess
|
||||
serverResetSuccess,
|
||||
input.setServerName.map { _ in () }.asObservable()
|
||||
)
|
||||
.map {
|
||||
[SectionModel(
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
//
|
||||
// GroupCellViewModel.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2021/6/8.
|
||||
// Copyright © 2021 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import RxCocoa
|
||||
import RxDataSources
|
||||
import RxSwift
|
||||
import UIKit
|
||||
|
||||
class GroupCellViewModel: ViewModel {
|
||||
let name = BehaviorRelay<String?>(value: nil)
|
||||
let checked = BehaviorRelay<Bool>(value: false)
|
||||
|
||||
init(groupFilterModel: GroupFilterModel) {
|
||||
self.name.accept(groupFilterModel.name)
|
||||
self.checked.accept(groupFilterModel.checked)
|
||||
}
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
//
|
||||
// GroupTableViewCell.swift
|
||||
// Bark
|
||||
//
|
||||
// Created by huangfeng on 2021/6/8.
|
||||
// Copyright © 2021 Fin. All rights reserved.
|
||||
//
|
||||
|
||||
import Material
|
||||
import UIKit
|
||||
|
||||
class GroupTableViewCell: BaseTableViewCell<GroupCellViewModel> {
|
||||
let nameLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.fontSize = 14
|
||||
label.textColor = BKColor.grey.darken4
|
||||
return label
|
||||
}()
|
||||
|
||||
let checkButton: BKButton = {
|
||||
let btn = BKButton()
|
||||
btn.setImage(UIImage(named: "baseline_radio_button_unchecked_black_24pt"), for: .normal)
|
||||
btn.setImage(UIImage(named: "baseline_check_circle_outline_black_24pt"), for: .selected)
|
||||
btn.tintColor = BKColor.grey.base
|
||||
btn.isUserInteractionEnabled = false
|
||||
return btn
|
||||
}()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
self.selectionStyle = .none
|
||||
self.backgroundColor = BKColor.background.secondary
|
||||
|
||||
self.contentView.addSubview(nameLabel)
|
||||
self.contentView.addSubview(checkButton)
|
||||
|
||||
checkButton.snp.makeConstraints { make in
|
||||
make.width.height.equalTo(24)
|
||||
make.left.equalToSuperview().offset(15)
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
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
|
||||
self?.viewModel?.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: GroupCellViewModel) {
|
||||
super.bindViewModel(model: model)
|
||||
|
||||
model.name
|
||||
.map { name in
|
||||
name ?? NSLocalizedString("default")
|
||||
}
|
||||
.bind(to: nameLabel.rx.text)
|
||||
.disposed(by: rx.reuseBag)
|
||||
|
||||
model.checked
|
||||
.bind(to: self.checkButton.rx.isSelected)
|
||||
.disposed(by: rx.reuseBag)
|
||||
|
||||
model.checked.subscribe(
|
||||
onNext: { [weak self] checked in
|
||||
self?.checkButton.tintColor = checked ? BKColor.lightBlue.darken3 : BKColor.grey.base
|
||||
}).disposed(by: rx.reuseBag)
|
||||
}
|
||||
}
|
||||
@ -101,7 +101,7 @@ class ServerListTableViewCell: BaseTableViewCell<ServerListTableViewCellViewMode
|
||||
override func bindViewModel(model: ServerListTableViewCellViewModel) {
|
||||
super.bindViewModel(model: model)
|
||||
|
||||
model.address
|
||||
model.name
|
||||
.bind(to: addressLabel.rx.text)
|
||||
.disposed(by: rx.reuseBag)
|
||||
|
||||
|
||||
@ -12,15 +12,19 @@ import UIKit
|
||||
class ServerListTableViewCellViewModel: ViewModel {
|
||||
let server: Server
|
||||
|
||||
let address: BehaviorRelay<String>
|
||||
let name: 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.name = BehaviorRelay<String>(value: {
|
||||
var serverName = URL(string: server.address)?.host ?? "Invalid Server"
|
||||
if let name = server.name, !name.isEmpty {
|
||||
serverName = name + "\n" + serverName
|
||||
}
|
||||
return serverName
|
||||
}())
|
||||
self.key = BehaviorRelay<String>(value: !server.key.isEmpty ? server.key : "none")
|
||||
self.state = BehaviorRelay<Bool>(value: server.state == .ok)
|
||||
|
||||
BIN
docs/_media/shortcuts_cn.png
Normal file
BIN
docs/_media/shortcuts_cn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 698 KiB |
@ -75,4 +75,12 @@ curl -X "POST" "https://api.day.app/push" \
|
||||
| ciphertext | 加密推送的密文 |
|
||||
| isArchive | 传 1 保存推送,传其他的不保存推送,不传按APP内设置来决定是否保存。 |
|
||||
| url | 点击推送时,跳转的URL ,支持URL Scheme 和 Universal Link |
|
||||
| action | 传 "none" 时,点击推送不会弹窗 |
|
||||
| action | 传 "none" 时,点击推送不会弹窗 |
|
||||
|
||||
## 快捷指令
|
||||
Bark 支持快捷指令直接发送推送,以下是当收到交警短信时,忽略静音模式持续响铃提醒用户的自动化示例。
|
||||
<img src="../_media/shortcuts_cn.png" />
|
||||
1. 创建个人自动化
|
||||
2. 选择信息、填写信息包含关键词触发自动化,选择立即执行,点击下一步
|
||||
3. 选择新建空白自动化,选择 Bark 发送推送到此设备快捷指令
|
||||
4. 填写推送配置,标题可以选择短信发件人、内容可以选择短信内容,或自己自定义。
|
||||
Loading…
x
Reference in New Issue
Block a user