Compare commits

...

8 Commits

Author SHA1 Message Date
Fin
cea0020f31 复制图文 2025-05-23 10:52:43 +08:00
Fin
4d5d90778d 保存图片到相册 2025-05-23 10:40:52 +08:00
Fin
625ef514fc 添加图片推送教程卡片 2025-05-23 09:48:04 +08:00
Fin
efe2f82964 调整消息列表图片高度 2025-05-23 09:47:34 +08:00
Fin
d16b0c8b4f 消息列表按实际图片尺寸显示 2025-05-22 18:03:37 +08:00
Fin
8526bf39f7 图片消息适配iPad 2025-05-22 15:34:56 +08:00
Fin
54a94e8b0f 支持传递大图
close: #162
2025-05-22 12:11:18 +08:00
Fin
469b234c5a 去掉临时通知权限 2025-05-22 12:08:21 +08:00
15 changed files with 292 additions and 15 deletions

View File

@ -8,6 +8,7 @@
import IQKeyboardManagerSwift
import IQKeyboardToolbarManager
import SVProgressHUD
import SwiftyStoreKit
import UIKit
import UserNotifications
@ -32,7 +33,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// Realm()
setupRealm()
SVProgressHUD.setMinimumDismissTimeInterval(1.5)
IQKeyboardManager.shared.isEnabled = true
IQKeyboardToolbarManager.shared.isEnabled = true
if #available(iOS 14, *), UIDevice.current.userInterfaceIdiom == .pad {

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Save the image to the photo library</string>
<key>CFBundleURLTypes</key>
<array>
<dict>

View File

@ -2117,6 +2117,64 @@
}
}
},
"imageParameter" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "To show a picture in a push banner and message list"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "プッシュバナーやメッセージ一覧に画像を表示するには、画像のURLを渡す必要があります。"
}
},
"tr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Gönderim afişlerinde ve mesaj listelerinde görsel göstermek için görsel URLsi iletilmelidir."
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "在推送横幅与消息列表中显示图片需传递图片url。"
}
}
}
},
"imagePushNotification" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Image Push Notification"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "画像プッシュ通知"
}
},
"tr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Görsel Bildirim Uyarısı"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "图片推送通知"
}
}
}
},
"import" : {
"extractionState" : "manual",
"localizations" : {
@ -2610,6 +2668,35 @@
}
}
},
"noPermission" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "No permission"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "権限がありません"
}
},
"tr" : {
"stringUnit" : {
"state" : "translated",
"value" : "İzin yok"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "没有权限"
}
}
}
},
"Notice1" : {
"extractionState" : "manual",
"localizations" : {
@ -3335,6 +3422,64 @@
}
}
},
"save" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Save"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "保存"
}
},
"tr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Kaydet"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "保存"
}
}
}
},
"saveSuccess" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Saved to your photos!"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "アルバムに保存したよ!"
}
},
"tr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Albüme kaydedildi!"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "已保存到相册!"
}
}
}
},
"SecureConnection" : {
"extractionState" : "manual",
"localizations" : {

View File

@ -59,7 +59,7 @@ class Client: NSObject {
func registerForRemoteNotifications() {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound, .badge, .criticalAlert, .provisional], completionHandler: { (_ granted: Bool, _: Error?) in
center.requestAuthorization(options: [.alert, .sound, .badge, .criticalAlert], completionHandler: { (_ granted: Bool, _: Error?) in
if granted {
dispatch_sync_safely_main_queue {
UIApplication.shared.registerForRemoteNotifications()

View File

@ -14,7 +14,7 @@ let kRealmDefaultConfiguration = {
let fileUrl = groupUrl?.appendingPathComponent("bark.realm")
let config = Realm.Configuration(
fileURL: fileUrl,
schemaVersion: 15,
schemaVersion: 16,
migrationBlock: { migration, oldSchemaVersion in
switch oldSchemaVersion {
case 0...13:

View File

@ -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, .provisional], completionHandler: { (_ granted: Bool, _: Error?) in
center.requestAuthorization(options: [.alert, .sound, .badge, .criticalAlert], completionHandler: { (_ granted: Bool, _: Error?) in
single(.success(granted))
})
return Disposables.create()

View File

@ -102,6 +102,11 @@ class HomeViewModel: ViewModel, ViewModelType {
notice: NSLocalizedString("urlParameter"),
queryParameter: "url=https://www.baidu.com"
),
PreviewModel(
body: NSLocalizedString("imagePushNotification"),
notice: NSLocalizedString("imageParameter"),
queryParameter: "image=https://day.app/assets/images/avatar.jpg"
),
PreviewModel(
body: "Copy Test",
notice: NSLocalizedString("copyParameter"),

View File

@ -13,6 +13,7 @@ import RxCocoa
import RxDataSources
import RxSwift
import UIKit
import UniformTypeIdentifiers
class MessageListViewController: BaseViewController<MessageListViewModel> {
lazy var deleteButton: UIBarButtonItem = {
@ -294,13 +295,22 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
self.navigationController?.present(alertController, animated: true, completion: nil)
}
private func alertMessage(message: MessageItemModel, sourceView: UIView, sourceCell: UITableViewCell) {
private func alertMessage(message: MessageItemModel, sourceView: MessageItemView, sourceCell: UITableViewCell) {
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
//
alertController.addAction(UIAlertAction(title: NSLocalizedString("Copy2"), style: .default, handler: { [weak self]
(_: UIAlertAction) in
UIPasteboard.general.string = message.attributedText?.string
if #available(iOS 14.0, *) {
var items = [[String: Any]]()
items.append([UTType.utf8PlainText.identifier: message.attributedText?.string ?? ""])
if let image = sourceView.imageView.image {
items.append([UTType.image.identifier: image])
}
UIPasteboard.general.items = items
} else {
UIPasteboard.general.string = message.attributedText?.string ?? ""
}
self?.showSnackbar(text: NSLocalizedString("Copy"))
}))
//

View File

@ -15,6 +15,7 @@ class Message: Object {
@objc dynamic var subtitle: String?
@objc dynamic var body: String?
@objc dynamic var url: String?
@objc dynamic var image: String?
@objc dynamic var group: String?
@objc dynamic var createDate: Date?

View File

@ -6,6 +6,7 @@
// Copyright © 2024 Fin. All rights reserved.
//
import Kingfisher
import UIKit
enum MessageListCellDateStyle {
@ -22,6 +23,8 @@ class MessageItemModel {
var attributedText: NSAttributedString?
var dateText: String?
var image: String?
var createDate: Date?
var dateStyle: MessageListCellDateStyle = .relative {
didSet {
@ -90,6 +93,7 @@ class MessageItemModel {
self.attributedText = text
self.createDate = message.createDate
self.image = message.image
defer {
self.dateStyle = .relative
}

View File

@ -30,6 +30,7 @@ class ArchiveProcessor: NotificationContentProcessor {
let body = alert?["body"] as? String
let url = userInfo["url"] as? String
let group = userInfo["group"] as? String
let image = userInfo["image"] as? String
try? realm?.write {
let message = Message()
@ -37,6 +38,7 @@ class ArchiveProcessor: NotificationContentProcessor {
message.subtitle = subtitle
message.body = body
message.url = url
message.image = image
message.group = group
message.createDate = Date()
realm?.add(message)

View File

@ -28,6 +28,7 @@ def pods
pod 'Kingfisher'
pod 'MercariQRScanner', :git => 'https://github.com/Finb/QRScanner'
pod 'DropDown'
pod 'ImageViewer.swift'
pod 'SwiftyStoreKit'
end

View File

@ -5,6 +5,7 @@ PODS:
- Differentiator (5.0.0)
- DropDown (2.3.13)
- FDFullscreenPopGesture (1.1)
- ImageViewer.swift (3.3.8)
- IQKeyboardCore (1.0.7)
- IQKeyboardManagerSwift/Core (8.0.0):
- IQKeyboardNotification
@ -74,6 +75,7 @@ DEPENDENCIES:
- DefaultsKit
- DropDown
- FDFullscreenPopGesture
- ImageViewer.swift
- IQKeyboardManagerSwift/IQKeyboardToolbarManager
- Kingfisher
- Material
@ -100,6 +102,7 @@ SPEC REPOS:
- Differentiator
- DropDown
- FDFullscreenPopGesture
- ImageViewer.swift
- IQKeyboardCore
- IQKeyboardManagerSwift
- IQKeyboardNotification
@ -141,6 +144,7 @@ SPEC CHECKSUMS:
Differentiator: e8497ceab83c1b10ca233716d547b9af21b9344d
DropDown: 8a2116376c1981888557f72ec2ffc9a5e0e456ec
FDFullscreenPopGesture: a8a620179e3d9c40e8e00256dcee1c1a27c6d0f0
ImageViewer.swift: 284cd8127d31af8e5938674fb9f8e695a4cdf6c6
IQKeyboardCore: cb7f0a9a17dd32599569f2f478c1418dc28bcebb
IQKeyboardManagerSwift: 0c6fbbaa2e60739e48d7cf59f25661471a7a3a65
IQKeyboardNotification: d7382c4466c5a5adef92c7452ebf861b36050088
@ -167,6 +171,6 @@ SPEC CHECKSUMS:
SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a
SwiftyStoreKit: 6b9c08810269f030586dac1fae8e75871a82e84a
PODFILE CHECKSUM: a7da3cf53c17d876cd2e822046dc7499c69fe470
PODFILE CHECKSUM: 3d8263a3dcdc33edad82e369b6e182961d45cc98
COCOAPODS: 1.16.2

View File

@ -6,6 +6,10 @@
// Copyright © 2024 Fin. All rights reserved.
//
import ImageViewer_swift
import Kingfisher
import Photos
import SVProgressHUD
import UIKit
class MessageItemView: UIView {
@ -13,6 +17,7 @@ class MessageItemView: UIView {
let view = UIView()
view.layer.cornerRadius = 10
view.backgroundColor = BKColor.background.secondary
view.clipsToBounds = true
return view
}()
@ -33,6 +38,22 @@ class MessageItemView: UIView {
return label
}()
let imageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 4
imageView.clipsToBounds = true
return imageView
}()
let contentStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 8
stackView.alignment = .leading
return stackView
}()
let dateLabel: UILabel = {
let label = BKLabel()
label.hitTestSlop = UIEdgeInsets(top: -5, left: -5, bottom: -5, right: -5)
@ -67,15 +88,25 @@ class MessageItemView: UIView {
}
}
var tapAction: ((_ message: MessageItemModel, _ sourceView: UIView) -> Void)?
var tapAction: ((_ message: MessageItemModel, _ sourceView: MessageItemView) -> Void)?
///
lazy var imageCache: ImageCache = {
let groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bark")
let cache = try? ImageCache(name: "shared", cacheDirectoryURL: groupUrl)
return cache ?? KingfisherManager.shared.cache
}()
init() {
super.init(frame: .zero)
self.backgroundColor = BKColor.background.primary
self.addSubview(panel)
panel.addSubview(bodyLabel)
panel.addSubview(contentStackView)
panel.addSubview(dateLabel)
panel.addSubview(blackMaskView)
contentStackView.addArrangedSubview(bodyLabel)
contentStackView.addArrangedSubview(imageView)
layoutView()
@ -104,17 +135,16 @@ class MessageItemView: UIView {
}
func layoutView() {
bodyLabel.snp.makeConstraints { make in
contentStackView.snp.makeConstraints { make in
make.top.equalTo(16)
make.left.equalTo(12)
make.right.equalTo(-12)
}
dateLabel.snp.makeConstraints { make in
make.left.equalTo(bodyLabel)
make.top.equalTo(bodyLabel.snp.bottom).offset(12)
make.left.equalTo(contentStackView)
make.top.equalTo(contentStackView.snp.bottom).offset(12)
make.bottom.equalTo(panel).offset(-12).priority(.medium)
}
panel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.right.equalToSuperview().offset(-16)
@ -136,5 +166,75 @@ extension MessageItemView {
func setMessage(message: MessageItemModel) {
self.bodyLabel.attributedText = message.attributedText
self.dateLabel.text = message.dateText
if let image = message.image {
imageView.isHidden = false
// loadDiskFileSynchronously
imageView.kf.setImage(with: URL(string: image), options: [.targetCache(imageCache), .keepCurrentImageWhileLoading, .loadDiskFileSynchronously]) { [weak self] result in
guard let self else { return }
guard let image = try? result.get().image else {
return
}
//
let isDarkMode = UIScreen.main.traitCollection.userInterfaceStyle == .dark
var options: [ImageViewerOption] = [
.closeIcon(UIImage(named: "back")!),
.theme(isDarkMode ? .dark : .light)
]
if #available(iOS 14.0, *) {
options.append(.rightNavItemTitle(NSLocalizedString("save"), onTap: { _ in
// image
self.saveImageToAlbum(image)
}))
}
self.imageView.setupImageViewer(options: options)
layoutImageView(image: image)
}
} else {
imageView.isHidden = true
}
}
func layoutImageView(image: UIImage) {
let scale = image.size.height / image.size.width
// iPad 500
var width = min(min(500, UIScreen.main.bounds.width - 32 - 24), image.width)
var height = width * scale
if height > 400 {
width = 400 / scale
height = 400
}
imageView.snp.remakeConstraints { make in
make.width.equalTo(width)
make.height.equalTo(height)
}
}
}
@available(iOS 14.0, *)
extension MessageItemView {
func saveImageToAlbum(_ image: UIImage) {
PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in
guard status == .authorized || status == .limited else {
DispatchQueue.main.async {
SVProgressHUD.showInfo(withStatus: NSLocalizedString("noPermission"))
}
return
}
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAsset(from: image)
}) { success, error in
DispatchQueue.main.async {
if success {
SVProgressHUD.showSuccess(withStatus: NSLocalizedString("saveSuccess"))
} else {
SVProgressHUD.showError(withStatus: error?.localizedDescription)
}
}
}
}
}
}

View File

@ -23,7 +23,7 @@ class MessageTableViewCell: UITableViewCell {
}
}
var tapAction: ((_ message: MessageItemModel, _ sourceView: UIView) -> Void)? {
var tapAction: ((_ message: MessageItemModel, _ sourceView: MessageItemView) -> Void)? {
didSet {
messageView.tapAction = tapAction
}
@ -143,7 +143,7 @@ class MessageGroupTableViewCell: UITableViewCell {
}
}
var tapAction: ((_ message: MessageItemModel, _ sourceView: UIView) -> Void)? = nil
var tapAction: ((_ message: MessageItemModel, _ sourceView: MessageItemView) -> Void)? = nil
///
var showGroupMessageAction: ((_ group: String?) -> Void)? = nil