session-ios/Session/Shared/HighlightMentionBackgroundView.swift
Morgan Pretty 1224e539ea Reduced unneeded DB write operations and fixed a few minor UI bugs
Updated the database to better support the application getting suspended (0xdead10cc crash)
Updated the SOGS message handling to delete messages based on a new 'deleted' flag instead of 'data' being null
Updated the code to prevent the typing indicator from needing a DB write block as frequently
Updated the code to stop any pending jobs when entering the background (in an attempt to prevent the database suspension from causing issues)
Removed the duplicate 'Capabilities.Capability' type (updated 'Capability.Variant' to work in the same way)
Fixed a bug where a number of icons (inc. the "download document" icon) were the wrong colour in dark mode
Fixed a bug where the '@You' highlight could incorrectly have it's width reduced in some cases (had protection to prevent it being larger than the line, but that is a valid case)
Fixed a bug where the JobRunner was starting the background (which could lead to trying to access the database once it had been suspended)
Updated to the latest version of GRDB
Added some logic to the BackgroundPoller process to try and stop processing if the timeout is triggered (will catch some cases but others will end up logging a bunch of "Database is suspended" errors)
Added in some protection to prevent future deferral loops in the JobRunner
2022-08-05 17:10:01 +10:00

159 lines
6.7 KiB
Swift

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
public extension NSAttributedString.Key {
static let currentUserMentionBackgroundColor: NSAttributedString.Key = NSAttributedString.Key(rawValue: "currentUserMentionBackgroundColor")
static let currentUserMentionBackgroundCornerRadius: NSAttributedString.Key = NSAttributedString.Key(rawValue: "currentUserMentionBackgroundCornerRadius")
static let currentUserMentionBackgroundPadding: NSAttributedString.Key = NSAttributedString.Key(rawValue: "currentUserMentionBackgroundPadding")
}
class HighlightMentionBackgroundView: UIView {
var maxPadding: CGFloat = 0
init() {
super.init(frame: .zero)
self.isOpaque = false
self.layer.zPosition = -1
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Functions
public func calculateMaxPadding(for attributedText: NSAttributedString) -> CGFloat {
var allMentionRadii: [CGFloat?] = []
let path: CGMutablePath = CGMutablePath()
path.addRect(CGRect(
x: 0,
y: 0,
width: CGFloat.greatestFiniteMagnitude,
height: CGFloat.greatestFiniteMagnitude
))
let framesetter = CTFramesetterCreateWithAttributedString(attributedText as CFAttributedString)
let frame: CTFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributedText.length), path, nil)
let lines: [CTLine] = frame.lines
lines.forEach { line in
let runs: [CTRun] = line.ctruns
runs.forEach { run in
let attributes: NSDictionary = CTRunGetAttributes(run)
allMentionRadii.append(
attributes
.value(forKey: NSAttributedString.Key.currentUserMentionBackgroundPadding.rawValue) as? CGFloat
)
}
}
return allMentionRadii
.compactMap { $0 }
.max()
.defaulting(to: 0)
}
// MARK: - Drawing
override func draw(_ rect: CGRect) {
guard
let superview: UITextView = (self.superview as? UITextView),
let context = UIGraphicsGetCurrentContext()
else { return }
// Need to invery the Y axis because iOS likes to render from the bottom left instead of the top left
context.textMatrix = .identity
context.translateBy(x: 0, y: bounds.size.height)
context.scaleBy(x: 1.0, y: -1.0)
// Note: Calculations MUST happen based on the 'superview' size as this class has extra padding which
// can result in calculations being off
let path = CGMutablePath()
let size = superview.sizeThatFits(CGSize(width: superview.bounds.width, height: .greatestFiniteMagnitude))
path.addRect(CGRect(x: 0, y: 0, width: size.width, height: size.height), transform: .identity)
let framesetter = CTFramesetterCreateWithAttributedString(superview.attributedText as CFAttributedString)
let frame: CTFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, superview.attributedText.length), path, nil)
let lines: [CTLine] = frame.lines
var origins = [CGPoint](repeating: .zero, count: lines.count)
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), &origins)
for lineIndex in 0..<lines.count {
let line = lines[lineIndex]
let runs: [CTRun] = line.ctruns
var ascent: CGFloat = 0
var descent: CGFloat = 0
var leading: CGFloat = 0
let lineWidth = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, &leading))
for run in runs {
let attributes: NSDictionary = CTRunGetAttributes(run)
guard let mentionBackgroundColor: UIColor = attributes.value(forKey: NSAttributedString.Key.currentUserMentionBackgroundColor.rawValue) as? UIColor else {
continue
}
let cornerRadius: CGFloat = (attributes
.value(forKey: NSAttributedString.Key.currentUserMentionBackgroundCornerRadius.rawValue) as? CGFloat)
.defaulting(to: 0)
let padding: CGFloat = (attributes
.value(forKey: NSAttributedString.Key.currentUserMentionBackgroundPadding.rawValue) as? CGFloat)
.defaulting(to: 0)
let range = CTRunGetStringRange(run)
var runBounds: CGRect = .zero
var runAscent: CGFloat = 0
var runDescent: CGFloat = 0
runBounds.size.width = CGFloat(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &runAscent, &runDescent, nil) + (padding * 2))
runBounds.size.height = (runAscent + runDescent + (padding * 2))
let xOffset: CGFloat = {
switch CTRunGetStatus(run) {
case .rightToLeft:
return CTLineGetOffsetForStringIndex(line, range.location + range.length, nil)
default:
return CTLineGetOffsetForStringIndex(line, range.location, nil)
}
}()
// HACK: This `extraYOffset` value is a hack to resolve a weird issue where the positioning
// seems to be slightly off every additional line of text we add (it doesn't seem to be related
// to line spacing or anything, more related to the bold mention text being positioned slightly
// differently from the non-bold text)
let extraYOffset: CGFloat = (CGFloat(lineIndex) * (runDescent / 12))
// Note: Changes to `origin.y` need to be inverted since the context has been flipped
runBounds.origin.x = origins[lineIndex].x + rect.origin.x + self.maxPadding + xOffset - padding
runBounds.origin.y = (
origins[lineIndex].y + rect.origin.y +
self.maxPadding -
padding -
runDescent -
extraYOffset
)
let path = UIBezierPath(roundedRect: runBounds, cornerRadius: cornerRadius)
mentionBackgroundColor.setFill()
path.fill()
}
}
}
}
extension CTFrame {
var lines: [CTLine] {
return ((CTFrameGetLines(self) as [AnyObject] as? [CTLine]) ?? [])
}
}
extension CTLine {
var ctruns: [CTRun] {
return ((CTLineGetGlyphRuns(self) as [AnyObject] as? [CTRun]) ?? [])
}
}