WIP
This commit is contained in:
parent
b3378992ed
commit
9112231f66
|
@ -109,11 +109,11 @@
|
|||
7B1D74AA27BCC16E0030B423 /* NSENotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */; };
|
||||
7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */; };
|
||||
7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */; };
|
||||
7B2E985829AC227C001792D7 /* UIContextualAction+Theming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2E985729AC227C001792D7 /* UIContextualAction+Theming.swift */; };
|
||||
7B46AAAF28766DF4001AF2DC /* AllMediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */; };
|
||||
7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; };
|
||||
7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; };
|
||||
7B50D64D28AC7CF80086CCEC /* silence.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 7B50D64C28AC7CF80086CCEC /* silence.aiff */; };
|
||||
7B521E0629A87CEA00C3C36A /* UIContextualAction+Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B521E0529A87CEA00C3C36A /* UIContextualAction+Session.swift */; };
|
||||
7B7037432834B81F000DCF35 /* ReactionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7037422834B81F000DCF35 /* ReactionContainerView.swift */; };
|
||||
7B7037452834BCC0000DCF35 /* ReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7037442834BCC0000DCF35 /* ReactionView.swift */; };
|
||||
7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */; };
|
||||
|
@ -1178,11 +1178,11 @@
|
|||
7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Timeout.swift"; sourceTree = "<group>"; };
|
||||
7B1D74AF27C365960030B423 /* Timer+MainThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timer+MainThread.swift"; sourceTree = "<group>"; };
|
||||
7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
7B2E985729AC227C001792D7 /* UIContextualAction+Theming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Theming.swift"; sourceTree = "<group>"; };
|
||||
7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllMediaViewController.swift; sourceTree = "<group>"; };
|
||||
7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = "<group>"; };
|
||||
7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = "<group>"; };
|
||||
7B50D64C28AC7CF80086CCEC /* silence.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = silence.aiff; sourceTree = "<group>"; };
|
||||
7B521E0529A87CEA00C3C36A /* UIContextualAction+Session.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Session.swift"; sourceTree = "<group>"; };
|
||||
7B7037422834B81F000DCF35 /* ReactionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionContainerView.swift; sourceTree = "<group>"; };
|
||||
7B7037442834BCC0000DCF35 /* ReactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionView.swift; sourceTree = "<group>"; };
|
||||
7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallBanner.swift; sourceTree = "<group>"; };
|
||||
|
@ -2584,7 +2584,6 @@
|
|||
FD52090828B59411006098F6 /* ScreenLockUI.swift */,
|
||||
FD37EA0828AA2D27003AE748 /* SessionTableViewModel.swift */,
|
||||
FD37EA0628AA2CCA003AE748 /* SessionTableViewController.swift */,
|
||||
7B521E0529A87CEA00C3C36A /* UIContextualAction+Session.swift */,
|
||||
);
|
||||
path = Shared;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2819,6 +2818,7 @@
|
|||
B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */,
|
||||
C33100272559000A00070591 /* UIView+Utilities.swift */,
|
||||
FD71161F28D97ABC00B47552 /* UIImage+Tinting.swift */,
|
||||
7B2E985729AC227C001792D7 /* UIContextualAction+Theming.swift */,
|
||||
);
|
||||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
|
@ -5142,6 +5142,7 @@
|
|||
FD37E9D528A1FCE8003AE748 /* Theme+OceanLight.swift in Sources */,
|
||||
FD37E9C828A1D73F003AE748 /* Theme+Colors.swift in Sources */,
|
||||
FD37EA0128A60473003AE748 /* UIKit+Theme.swift in Sources */,
|
||||
7B2E985829AC227C001792D7 /* UIContextualAction+Theming.swift in Sources */,
|
||||
FD37E9CF28A1EB1B003AE748 /* Theme.swift in Sources */,
|
||||
C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */,
|
||||
FD37E9F628A5F106003AE748 /* Configuration.swift in Sources */,
|
||||
|
@ -5715,7 +5716,6 @@
|
|||
4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */,
|
||||
B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */,
|
||||
45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */,
|
||||
7B521E0629A87CEA00C3C36A /* UIContextualAction+Session.swift in Sources */,
|
||||
7B9F71D42852EEE2006DFE7B /* Emoji+Name.swift in Sources */,
|
||||
4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */,
|
||||
C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */,
|
||||
|
|
|
@ -626,7 +626,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi
|
|||
case .messageRequests:
|
||||
return nil
|
||||
case .threads:
|
||||
|
||||
let threadViewModel: SessionThreadViewModel = section.elements[indexPath.row]
|
||||
return UISwipeActionsConfiguration(actions: [ ])
|
||||
default: return nil
|
||||
}
|
||||
|
@ -649,8 +649,15 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi
|
|||
case .threads:
|
||||
let threadViewModel: SessionThreadViewModel = section.elements[indexPath.row]
|
||||
let delete: UIContextualAction = UIContextualAction(
|
||||
style: .destructive,
|
||||
title: "TXT_DELETE_TITLE".localized()
|
||||
title: "TXT_DELETE_TITLE".localized(),
|
||||
icon: UIImage(named: "icon_bin"),
|
||||
iconHeight: 5,
|
||||
themeTintColor: .textPrimary,
|
||||
themeBackgroundColor: .conversationButton_swipeDestructive,
|
||||
side: .trailing,
|
||||
actionIndex: 2,
|
||||
indexPath: indexPath,
|
||||
tableView: tableView
|
||||
) { [weak self] _, _, completionHandler in
|
||||
let confirmationModal: ConfirmationModal = ConfirmationModal(
|
||||
info: ConfirmationModal.Info(
|
||||
|
@ -679,7 +686,6 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi
|
|||
self?.present(confirmationModal, animated: true, completion: nil)
|
||||
}
|
||||
delete.themeBackgroundColor = .conversationButton_swipeDestructive
|
||||
delete.setupSessionStyle(with: UIImage(systemName: "trash"))
|
||||
|
||||
let pin: UIContextualAction = UIContextualAction(
|
||||
style: .normal,
|
||||
|
@ -703,7 +709,6 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi
|
|||
}
|
||||
}
|
||||
pin.themeBackgroundColor = .conversationButton_swipeTertiary
|
||||
pin.setupSessionStyle(with: UIImage(systemName: "pin"))
|
||||
|
||||
guard threadViewModel.threadVariant == .contact && !threadViewModel.threadIsNoteToSelf else {
|
||||
return UISwipeActionsConfiguration(actions: [ delete, pin ])
|
||||
|
@ -749,6 +754,14 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi
|
|||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
|
||||
UIContextualAction.willBeginEditing(indexPath: indexPath, tableView: tableView)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
|
||||
UIContextualAction.didEndEditing(indexPath: indexPath, tableView: tableView)
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
||||
func handleContinueButtonTapped(from seedReminderView: SeedReminderView) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,6 +34,12 @@ public extension Dictionary {
|
|||
return self[key]
|
||||
}
|
||||
|
||||
func getting(_ key: Key?) -> Value? {
|
||||
guard let key: Key = key else { return nil }
|
||||
|
||||
return self[key]
|
||||
}
|
||||
|
||||
func setting(_ key: Key?, _ value: Value?) -> [Key: Value] {
|
||||
guard let key: Key = key else { return self }
|
||||
|
||||
|
|
Loading…
Reference in New Issue