mirror of https://github.com/oxen-io/session-ios
WIP
parent
b3378992ed
commit
9112231f66
@ -1,42 +0,0 @@
|
|||||||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
extension UIContextualAction {
|
|
||||||
|
|
||||||
func setupSessionStyle(with image: UIImage?) {
|
|
||||||
guard let title = self.title, let image = image else {
|
|
||||||
self.image = image
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let text = NSMutableAttributedString(string: "")
|
|
||||||
let attachment = NSTextAttachment()
|
|
||||||
attachment.image = image.withTintColor(.white)
|
|
||||||
text.append(NSAttributedString(attachment: attachment))
|
|
||||||
text.append(
|
|
||||||
NSAttributedString(
|
|
||||||
string: "\n\(title)",
|
|
||||||
attributes: [
|
|
||||||
.font : UIFont.systemFont(ofSize: Values.smallFontSize),
|
|
||||||
.foregroundColor : UIColor.white
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
|
|
||||||
label.textAlignment = .center
|
|
||||||
label.numberOfLines = 2
|
|
||||||
label.attributedText = text
|
|
||||||
|
|
||||||
let renderer = UIGraphicsImageRenderer(bounds: label.bounds)
|
|
||||||
let renderedImage = renderer.image { context in
|
|
||||||
label.layer.render(in: context.cgContext)
|
|
||||||
}
|
|
||||||
if let cgImage = renderedImage.cgImage {
|
|
||||||
let finalImage = UIImage(cgImage: cgImage, scale: UIScreen.main.scale, orientation: .up)
|
|
||||||
self.image = finalImage
|
|
||||||
self.title = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,181 @@
|
|||||||
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
|
public extension UIContextualAction {
|
||||||
|
private static var lookupMap: Atomic<[Int: [String: [Int: ThemeValue]]]> = Atomic([:])
|
||||||
|
|
||||||
|
enum Side: Int {
|
||||||
|
case leading
|
||||||
|
case trailing
|
||||||
|
|
||||||
|
func key(for indexPath: IndexPath) -> String {
|
||||||
|
return "\(indexPath.section)-\(indexPath.row)-\(rawValue)"
|
||||||
|
}
|
||||||
|
|
||||||
|
init?(for view: UIView) {
|
||||||
|
guard view.frame.minX == 0 else {
|
||||||
|
self = .trailing
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self = .leading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convenience init(
|
||||||
|
title: String? = nil,
|
||||||
|
icon: UIImage? = nil,
|
||||||
|
iconHeight: CGFloat = Values.mediumFontSize,
|
||||||
|
themeTintColor: ThemeValue = .textPrimary,
|
||||||
|
themeBackgroundColor: ThemeValue,
|
||||||
|
side: Side,
|
||||||
|
actionIndex: Int,
|
||||||
|
indexPath: IndexPath,
|
||||||
|
tableView: UITableView,
|
||||||
|
handler: @escaping UIContextualAction.Handler
|
||||||
|
) {
|
||||||
|
self.init(style: .normal, title: title, handler: handler)
|
||||||
|
self.image = UIContextualAction
|
||||||
|
.imageWith(
|
||||||
|
title: title,
|
||||||
|
icon: icon,
|
||||||
|
iconHeight: iconHeight,
|
||||||
|
themeTintColor: themeTintColor
|
||||||
|
)?
|
||||||
|
.withRenderingMode(.alwaysTemplate)
|
||||||
|
self.themeBackgroundColor = themeBackgroundColor
|
||||||
|
|
||||||
|
UIContextualAction.lookupMap.mutate {
|
||||||
|
$0[tableView.hashValue] = ($0[tableView.hashValue] ?? [:])
|
||||||
|
.setting(
|
||||||
|
side.key(for: indexPath),
|
||||||
|
(($0[tableView.hashValue] ?? [:])[side.key(for: indexPath)] ?? [:])
|
||||||
|
.setting(actionIndex, themeTintColor)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func imageWith(
|
||||||
|
title: String?,
|
||||||
|
icon: UIImage?,
|
||||||
|
iconHeight: CGFloat,
|
||||||
|
themeTintColor: ThemeValue
|
||||||
|
) -> UIImage? {
|
||||||
|
let stackView: UIStackView = UIStackView()
|
||||||
|
stackView.axis = .vertical
|
||||||
|
stackView.alignment = .center
|
||||||
|
stackView.spacing = 3
|
||||||
|
|
||||||
|
if let icon: UIImage = icon {
|
||||||
|
let aspectRatio: CGFloat = (icon.size.width / icon.size.height)
|
||||||
|
let imageView: UIImageView = UIImageView(image: icon)
|
||||||
|
imageView.frame = CGRect(x: 0, y: 0, width: (iconHeight * aspectRatio), height: iconHeight)
|
||||||
|
imageView.contentMode = .scaleAspectFit
|
||||||
|
imageView.themeTintColor = themeTintColor
|
||||||
|
stackView.addArrangedSubview(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let title: String = title {
|
||||||
|
let label: UILabel = UILabel()
|
||||||
|
label.font = .systemFont(ofSize: Values.verySmallFontSize)
|
||||||
|
label.text = title
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.themeTextColor = themeTintColor
|
||||||
|
label.minimumScaleFactor = 0.75
|
||||||
|
label.numberOfLines = (title.components(separatedBy: " ").count > 1 ? 2 : 1)
|
||||||
|
label.frame = CGRect(
|
||||||
|
origin: .zero,
|
||||||
|
// Note: It looks like there is a semi-max width of 68px for images in the swipe actions
|
||||||
|
// if the image ends up larger then there an odd behaviour can occur where 8/10 times the
|
||||||
|
// image is scaled down to fit, but ocassionally (primarily if you hide the action and
|
||||||
|
// immediately swipe to show it again once the cell hits the edge of the screen) the image
|
||||||
|
// won't be scaled down but will be full size - appearing as if two different images are used
|
||||||
|
size: label.sizeThatFits(CGSize(width: 68, height: 999))
|
||||||
|
)
|
||||||
|
label.set(.width, to: label.frame.width)
|
||||||
|
|
||||||
|
stackView.addArrangedSubview(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
stackView.frame = CGRect(
|
||||||
|
origin: .zero,
|
||||||
|
size: stackView.systemLayoutSizeFitting(CGSize(width: 999, height: 999))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Based on https://stackoverflow.com/a/41288197/1118398
|
||||||
|
let renderFormat: UIGraphicsImageRendererFormat = UIGraphicsImageRendererFormat()
|
||||||
|
renderFormat.scale = UIScreen.main.scale
|
||||||
|
|
||||||
|
let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer(
|
||||||
|
size: stackView.bounds.size,
|
||||||
|
format: renderFormat
|
||||||
|
)
|
||||||
|
return renderer.image { rendererContext in
|
||||||
|
stackView.layer.render(in: rendererContext.cgContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func firstSubviewOfType<T>(in superview: UIView) -> T? {
|
||||||
|
guard !(superview is T) else { return superview as? T }
|
||||||
|
guard !superview.subviews.isEmpty else { return nil }
|
||||||
|
|
||||||
|
for subview in superview.subviews {
|
||||||
|
if let result: T = firstSubviewOfType(in: subview) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
static func willBeginEditing(indexPath: IndexPath, tableView: UITableView) {
|
||||||
|
guard
|
||||||
|
let targetCell: UITableViewCell = tableView.cellForRow(at: indexPath),
|
||||||
|
targetCell.superview != tableView,
|
||||||
|
let targetSuperview: UIView = targetCell.superview?
|
||||||
|
.subviews
|
||||||
|
.filter({ $0 != targetCell })
|
||||||
|
.first,
|
||||||
|
let side: Side = Side(for: targetSuperview),
|
||||||
|
let themeMap: [Int: ThemeValue] = UIContextualAction.lookupMap.wrappedValue
|
||||||
|
.getting(tableView.hashValue)?
|
||||||
|
.getting(side.key(for: indexPath)),
|
||||||
|
targetSuperview.subviews.count == themeMap.count
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
let targetViews: [UIImageView] = targetSuperview.subviews
|
||||||
|
.compactMap { subview in firstSubviewOfType(in: subview) }
|
||||||
|
|
||||||
|
guard targetViews.count == themeMap.count else { return }
|
||||||
|
|
||||||
|
// Set the imageView and background colours (so they change correctly when the theme changes)
|
||||||
|
targetViews.enumerated().forEach { index, targetView in
|
||||||
|
guard let themeTintColor: ThemeValue = themeMap[index] else { return }
|
||||||
|
|
||||||
|
targetView.themeTintColor = themeTintColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func didEndEditing(indexPath: IndexPath?, tableView: UITableView) {
|
||||||
|
guard let indexPath: IndexPath = indexPath else { return }
|
||||||
|
|
||||||
|
let leadingKey: String = Side.leading.key(for: indexPath)
|
||||||
|
let trailingKey: String = Side.trailing.key(for: indexPath)
|
||||||
|
|
||||||
|
guard
|
||||||
|
UIContextualAction.lookupMap.wrappedValue[tableView.hashValue]?[leadingKey] != nil ||
|
||||||
|
UIContextualAction.lookupMap.wrappedValue[tableView.hashValue]?[trailingKey] != nil
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
UIContextualAction.lookupMap.mutate {
|
||||||
|
$0[tableView.hashValue]?[leadingKey] = nil
|
||||||
|
$0[tableView.hashValue]?[trailingKey] = nil
|
||||||
|
|
||||||
|
if $0[tableView.hashValue]?.isEmpty == true {
|
||||||
|
$0[tableView.hashValue] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue