Compare commits

...

8 Commits

Author SHA1 Message Date
Fin
8b49d6cc96 优化重要警告判断代码 2025-01-24 14:47:44 +08:00
Fin
3513d2afb3 删除直接推送的教程 2025-01-23 14:35:24 +08:00
Fin
6a98d93031 update doc 2025-01-23 12:25:10 +08:00
Fin
11233dac73 服务器弹出错误提示时引导查看FAQ 2025-01-23 10:22:37 +08:00
Fin
c0dfb5d2b4 删除自定义铃声时,一起删除 call 生成的长铃声。 2025-01-23 09:47:52 +08:00
Fin
294f8224f0 调整代码 2025-01-21 12:12:00 +08:00
Fin
3cf72b107e 调整持续响铃文案 2025-01-20 15:08:33 +08:00
Fin
23cc968647 update doc 2025-01-20 12:04:11 +08:00
14 changed files with 136 additions and 152 deletions

View File

@ -104,80 +104,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
private func notificatonHandler(userInfo: [AnyHashable: Any]) {
let viewController = Client.shared.currentSnackbarController
func presentController() {
let alert = (userInfo["aps"] as? [String: Any])?["alert"] as? [String: Any]
let title = alert?["title"] as? String
let subtitle = alert?["subtitle"] as? String
let body = alert?["body"] as? String
let url: URL? = {
if let url = userInfo["url"] as? String {
return URL(string: url)
}
return nil
}()
if let action = userInfo["action"] as? String, action == "none" {
return
}
// URL
if let url = url {
Client.shared.openUrl(url: url)
return
}
let alertController = UIAlertController(title: title, message: body, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("CopyContent"), style: .default, handler: { _ in
if let copy = userInfo["copy"] as? String {
UIPasteboard.general.string = copy
} else {
UIPasteboard.general.string = body
}
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("MoreActions"), style: .default, handler: { _ in
var shareContent = ""
if let title = title {
shareContent += "\(title)\n"
}
if let subtitle = subtitle {
shareContent += "\(subtitle)\n"
}
if let body = body {
shareContent += "\(body)\n"
}
for (key, value) in userInfo {
if ["aps", "title", "subtitle", "body", "url"].contains((key as? String) ?? "") {
continue
}
shareContent += "\(key): \(value) \n"
}
var items: [Any] = []
items.append(shareContent)
if let url = url {
items.append(url)
}
let controller = Client.shared.window?.rootViewController
let activityController = UIActivityViewController(activityItems: items,
applicationActivities: nil)
if let popover = activityController.popoverPresentationController {
popover.sourceView = controller?.view
popover.sourceRect = CGRect(x: controller?.view.bounds.midX ?? 0, y: controller?.view.bounds.midY ?? 0, width: 0, height: 0)
}
controller?.present(activityController, animated: true, completion: nil)
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
viewController?.present(alertController, animated: true, completion: nil)
if let action = userInfo["action"] as? String, action == "none" {
return
}
if let presentedController = viewController?.presentedViewController {
presentedController.dismiss(animated: false) {
presentController()
}
} else {
presentController()
// URL
if let url = try? (userInfo["url"] as? String)?.asURL() {
Client.shared.openUrl(url: url)
return
}
alertNotification(userInfo: userInfo)
}
func applicationWillResignActive(_ application: UIApplication) {
@ -219,3 +156,64 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
return false
}
}
extension AppDelegate {
func alertNotification(userInfo: [AnyHashable: Any]) {
let alert = (userInfo["aps"] as? [String: Any])?["alert"] as? [String: Any]
let title = alert?["title"] as? String
let subtitle = alert?["subtitle"] as? String
let body = alert?["body"] as? String
let url = try? (userInfo["url"] as? String)?.asURL()
let alertController = UIAlertController(title: title, message: body, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("CopyContent"), style: .default, handler: { _ in
if let copy = userInfo["copy"] as? String {
UIPasteboard.general.string = copy
} else {
UIPasteboard.general.string = body
}
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("MoreActions"), style: .default, handler: { _ in
var shareContent = ""
if let title = title {
shareContent += "\(title)\n"
}
if let subtitle = subtitle {
shareContent += "\(subtitle)\n"
}
if let body = body {
shareContent += "\(body)\n"
}
for (key, value) in userInfo {
if ["aps", "title", "subtitle", "body", "url"].contains((key as? String) ?? "") {
continue
}
shareContent += "\(key): \(value) \n"
}
var items: [Any] = []
items.append(shareContent)
if let url = url {
items.append(url)
}
let controller = Client.shared.window?.rootViewController
let activityController = UIActivityViewController(activityItems: items,
applicationActivities: nil)
if let popover = activityController.popoverPresentationController {
popover.sourceView = controller?.view
popover.sourceRect = CGRect(x: controller?.view.bounds.midX ?? 0, y: controller?.view.bounds.midY ?? 0, width: 0, height: 0)
}
controller?.present(activityController, animated: true, completion: nil)
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
let viewController = Client.shared.currentSnackbarController
if let presentedController = viewController?.presentedViewController {
presentedController.dismiss(animated: false) {
viewController?.present(alertController, animated: true, completion: nil)
}
} else {
viewController?.present(alertController, animated: true, completion: nil)
}
}
}

View File

@ -2605,19 +2605,19 @@
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Keep playing the ringtone for 30 seconds. "
"value" : "Keep playing the ringtone for 30 seconds. With the level=critical parameter, it can ring continuously even in silent mode."
}
},
"tr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Zili 30 saniye boyunca çalmaya devam ettir. "
"value" : "Zil sesi 30 saniye boyunca kesintisiz çalar. level=critical parametresi ile sessiz modda bile sürekli çalabilir."
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "持续播放铃声 30 秒。"
"value" : "持续播放铃声 30 秒,配合 level=critical 参数可在静音模式下持续响铃。"
}
}
}
@ -3337,4 +3337,4 @@
}
},
"version" : "1.0"
}
}

View File

@ -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)
@ -87,7 +87,7 @@ class HomeViewController: BaseViewController<HomeViewModel> {
let startRequestAuthorization: () -> Observable<Bool> = {
Single<Bool>.create { single -> Disposable in
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound, .badge, .criticalAlert], completionHandler: { (_ granted: Bool, _: Error?) -> Void in
center.requestAuthorization(options: [.alert, .sound, .badge, .criticalAlert], completionHandler: { (_ granted: Bool, _: Error?) in
single(.success(granted))
})
return Disposables.create()
@ -166,6 +166,13 @@ class HomeViewController: BaseViewController<HomeViewModel> {
})
.disposed(by: rx.disposeBag)
// FAQ
output.alertServerError
.drive(onNext: { [weak self] error in
self?.alertServerError(error: error)
})
.disposed(by: rx.disposeBag)
// startButton
output.startButtonEnable
.drive(self.startButton.rx.isEnabled)
@ -198,11 +205,9 @@ class HomeViewController: BaseViewController<HomeViewModel> {
var viewController: UIViewController?
if let viewModel = viewModel as? NewServerViewModel {
viewController = NewServerViewController(viewModel: viewModel)
}
else if let viewModel = viewModel as? SoundsViewModel {
} else if let viewModel = viewModel as? SoundsViewModel {
viewController = SoundsViewController(viewModel: viewModel)
}
else if let viewModel = viewModel as? CryptoSettingViewModel {
} else if let viewModel = viewModel as? CryptoSettingViewModel {
self.navigationController?.present(BarkNavigationController(rootViewController: CryptoSettingController(viewModel: viewModel)), animated: true)
return
}
@ -219,4 +224,16 @@ class HomeViewController: BaseViewController<HomeViewModel> {
self.navigationController?.present(controller, animated: true, completion: nil)
}
}
func alertServerError(error: String) {
let alertController = UIAlertController(title: NSLocalizedString("ServerError"), message: error, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("faq"), style: .default, handler: { [weak self] _ in
guard let url = try? NSLocalizedString("faqUrl").asURL() else {
return
}
self?.navigationController?.present(BarkSFSafariViewController(url: url), animated: true, completion: nil)
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
}

View File

@ -32,6 +32,7 @@ class HomeViewModel: ViewModel, ViewModelType {
let clienStateChanged: Driver<Client.ClienState>
let tableViewHidden: Driver<Bool>
let showSnackbar: Driver<String>
let alertServerError: Driver<String>
let startButtonEnable: Driver<Bool>
let copy: Driver<String>
let preview: Driver<URL>
@ -119,6 +120,9 @@ class HomeViewModel: ViewModel, ViewModelType {
)
]
/// 2FAQ
private var serverErrorCount = 0
func transform(input: Input) -> Output {
let title = BehaviorRelay(value: ServerManager.shared.currentServer.host)
@ -166,6 +170,7 @@ class HomeViewModel: ViewModel, ViewModelType {
.asDriver(onErrorJustReturn: false)
let showSnackbar = PublishRelay<String>()
let alertServerError = PublishRelay<String>()
//
tableViewHidden
@ -187,11 +192,18 @@ class HomeViewModel: ViewModel, ViewModelType {
.map { _ in () }
// client state
input.clientState.drive(onNext: { state in
input.clientState.drive(onNext: { [weak self] state in
guard let self else { return }
switch state {
case .ok: break
case .serverError(let error):
showSnackbar.accept("\(NSLocalizedString("ServerError")): \(error.rawString())")
if serverErrorCount < 2 {
showSnackbar.accept("\(NSLocalizedString("ServerError")): \(error.rawString())")
} else {
alertServerError.accept(error.rawString())
}
serverErrorCount += 1
default: break
}
// url scheme state便
@ -220,6 +232,7 @@ class HomeViewModel: ViewModel, ViewModelType {
clienStateChanged: clienState.asDriver(onErrorDriveWith: .empty()),
tableViewHidden: tableViewHidden,
showSnackbar: showSnackbar.asDriver(onErrorDriveWith: .empty()),
alertServerError: alertServerError.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()) }),

View File

@ -91,7 +91,7 @@ class SoundsViewModel: ViewModel, ViewModelType {
guard case SoundItem.sound(let model) = item else {
return
}
self.dependencies.soundFileStorage.deleteSound(url: model.model.url)
self.dependencies.soundFileStorage.deleteSound(name: model.model.url.lastPathComponent)
}).disposed(by: rx.disposeBag)
//
@ -169,7 +169,7 @@ class SoundsViewModel: ViewModel, ViewModelType {
///
protocol SoundFileStorageProtocol {
func saveSound(url: URL)
func deleteSound(url: URL)
func deleteSound(name: String)
}
/// /Library/Sounds
@ -189,9 +189,16 @@ class SoundFileStorage: SoundFileStorageProtocol {
try? fileManager.copyItem(at: url, to: soundUrl)
}
func deleteSound(url: URL) {
// sounds
try? fileManager.removeItem(at: url)
func deleteSound(name: String) {
guard let soundsDirectoryUrl = getSoundsDirectory() else {
return
}
let soundUrl = soundsDirectoryUrl.appendingPathComponent(name)
let callSoundUrl = soundsDirectoryUrl.appendingPathComponent("\(kBarkSoundPrefix).\(name)")
// sounds
try? fileManager.removeItem(at: soundUrl)
// call=1
try? fileManager.removeItem(at: callSoundUrl)
}
/// Library Sounds

View File

@ -40,7 +40,7 @@ extension CallProcessor {
}
if let longSoundUrl = getLongSound(soundName: soundName, soundType: soundType) {
if let level = content.userInfo["level"] as? String, level == "critical" {
if content.isCritical {
LevelProcessor.setCriticalSound(content: content, soundName: longSoundUrl.lastPathComponent)
} else {
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: longSoundUrl.lastPathComponent))

View File

@ -15,7 +15,7 @@ class LevelProcessor: NotificationContentProcessor {
return bestAttemptContent
}
if let level = bestAttemptContent.userInfo["level"] as? String, level == "critical" {
if bestAttemptContent.isCritical {
//
LevelProcessor.setCriticalSound(content: bestAttemptContent)
return bestAttemptContent
@ -39,7 +39,7 @@ class LevelProcessor: NotificationContentProcessor {
extension LevelProcessor {
class func setCriticalSound(content bestAttemptContent: UNMutableNotificationContent, soundName: String? = nil) {
guard let level = bestAttemptContent.userInfo["level"] as? String, level == "critical" else {
guard bestAttemptContent.isCritical else {
return
}
//

View File

@ -28,7 +28,6 @@ APP在维持期间不会有任何形式的收费与广告各位彦祖放
- [常见问题](/faq)
- **服务端**
- [部署服务](/deploy)
- [直接推送](/apns)
- [批量推送](/batch)
- [编译代码](/build)
- [推送证书](/cert)

View File

@ -5,7 +5,6 @@
- [常见问题](/faq)
- **服务端**
- [部署服务](/deploy)
- [直接推送](/apns)
- [批量送送](/batch)
- [编译代码](/build)
- [推送证书](/cert)

View File

@ -1,55 +0,0 @@
### 直接调用APNS接口
如果有设备的 DeviceToken可在APP中查看就可以调用苹果APNS接口直接给设备发推送APP中也无需添加服务器。<br>
以下是命令行发推送示例:
```shell
# 设置环境变量
# 下载 key https://raw.githubusercontent.com/Finb/bark-server/master/deploy/AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8
# 将 key 文件路径填到下面
TOKEN_KEY_FILE_NAME=
# 从 app 设置中复制 DeviceToken 到这
DEVICE_TOKEN=
#下面的不要修改
TEAM_ID=5U8LBRXG3A
AUTH_KEY_ID=LH4T9V5U4R
TOPIC=me.fin.bark
APNS_HOST_NAME=api.push.apple.com
# 生成TOKEN
JWT_ISSUE_TIME=$(date +%s)
JWT_HEADER=$(printf '{ "alg": "ES256", "kid": "%s" }' "${AUTH_KEY_ID}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
JWT_CLAIMS=$(printf '{ "iss": "%s", "iat": %d }' "${TEAM_ID}" "${JWT_ISSUE_TIME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
JWT_HEADER_CLAIMS="${JWT_HEADER}.${JWT_CLAIMS}"
JWT_SIGNED_HEADER_CLAIMS=$(printf "${JWT_HEADER_CLAIMS}" | openssl dgst -binary -sha256 -sign "${TOKEN_KEY_FILE_NAME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
# 如果有条件,最好改进脚本缓存此 Token。Token 30分钟内复用同一个每过30分钟重新生成
# 苹果文档指明 TOKEN 生成间隔最短20分钟TOKEN 有效期最长60分钟
# 间隔过短重复生成会生成失败TOKEN 超过1小时不重新生成就不能推送
# 但经我不负责任的简单测试可以短时间内正常生成
# 此处仅提醒,或许可能因频繁生成 TOKEN 导致推送失败
AUTHENTICATION_TOKEN="${JWT_HEADER}.${JWT_CLAIMS}.${JWT_SIGNED_HEADER_CLAIMS}"
#发送推送
curl -v --header "apns-topic: $TOPIC" --header "apns-push-type: alert" --header "authorization: bearer $AUTHENTICATION_TOKEN" --data '{"aps":{"alert":"test"}}' --http2 https://${APNS_HOST_NAME}/3/device/${DEVICE_TOKEN}
```
### 推送参数格式
参考 https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification<br>
一定要带上 "mutable-content" : 1 ,否则推送扩展不执行,不会保存推送。<br>
示例:
```js
{
"aps": {
"mutable-content": 1,
"alert": {
"title" : "title",
"body": "body"
},
"category": "myNotificationCategory",
"sound": "minuet.caf"
},
"icon": "https://day.app/assets/images/avatar.jpg"
}
```

View File

@ -28,7 +28,6 @@ Sponsors[https://github.com/sponsors/Finb](https://github.com/sponsors/Finb)
- [FAQs](/en-us/faq)
- **Server**
- [Deploy](/en-us/deploy)
- [Direct Push](/en-us/apns)
- [Build](/en-us/build)
- [Certificate](/en-us/cert)
- [Privacy](/en-us/privacy)

View File

@ -1,3 +1,12 @@
#### 杭州移动无法访问 https://api.day.app
杭州移动运营商阻断了请求(通常可能会持续一段时间,几天或几周都有可能),可以自部署服务端或暂时替换下域名使用 <br>
https://api.day.app <br>
替换为 <br>
https://api.bbark.top <br>
这两个域名是同一台服务器通常只需简单的在发送端修改下域名即可key不用改 app 只是个接收端改不改无所谓。 <br>
也可以在 APP 内新增 https://api.bbark.top 服务器长期使用。
#### 无法收到推送
在 App 设置中检查 Device Token 是否正常。如果不正常,参考 [这里](#DeviceToken显示未知)<br/>
如果正常,可以重启下设备,如果还不能接收到推送,检查推送请求返回状态码是否为 code 200。<br/>

View File

@ -28,7 +28,6 @@ Sponsorlar[https://github.com/sponsors/Finb](https://github.com/sponsors/Finb
- [SSS](/tr/faq)
- **Sunucu**
- [Dağıtım](/tr/deploy)
- [Doğrudan Gönderim](/tr/apns)
- [Oluşturmak](/tr/build)
- [Sertifika](/tr/cert)
- [Gizlilik](/tr/privacy)

View File

@ -5,7 +5,6 @@
- [SSS](/tr/faq)
- **Sunucu**
- [Dağıtım](/tr/deploy)
- [Doğrudan Gönderim](/tr/apns)
- [Oluşturmak](/tr/build)
- [Sertifika](/tr/cert)
- [Gizlilik](/tr/privacy)