Improved message selection indicators in chat.

• use SelectionIndicatorView in chat.
• modify SelectionIndicatorView to allow to configure ring color.
• improve legibility by using custom shade of gray for selection indicator in "not selected" state in chat when in light mode and with a wallpaper set.
This commit is contained in:
Igor Solomennikov 2026-05-15 14:35:43 -07:00 committed by GitHub
parent b5838a1afc
commit 296aa8cc46
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 42 additions and 49 deletions

View File

@ -6,63 +6,41 @@
import SignalServiceKit
import SignalUI
/// ManualLayoutView wrapper around SelectionIndicatorView.
class MessageSelectionView: ManualLayoutView {
var isSelected: Bool = false {
didSet {
selectedView.isHidden = !isSelected
unselectedView.isHidden = isSelected
private let selectionIndicatorView = SelectionIndicatorView(style: .list)
var isSelected: Bool {
get {
selectionIndicatorView.isSelected
}
set {
selectionIndicatorView.isSelected = newValue
}
}
init() {
super.init(name: "MessageSelectionView")
addSubviewToCenterOnSuperview(selectedView, size: .square(Self.circleDiameter))
addSubviewToCenterOnSuperview(unselectedView, size: .square(Self.circleDiameter))
addLayoutBlock { view in
guard let selectionView = view as? MessageSelectionView else { return }
selectionView.checkmarkIcon.center = selectionView.selectedView.bounds.center
}
selectedView.isHidden = !isSelected
addSubviewToFillSuperviewEdges(selectionIndicatorView)
}
static var preferredSize: CGSize {
CGSize(square: ConversationStyle.selectionViewWidth)
CGSize(square: SelectionIndicatorView.preferredSize)
}
private static var circleDiameter: CGFloat {
// 22 dp as per spec
ConversationStyle.selectionViewWidth - 2
}
private static var emptyCheckmarkStrokeLineWidth: CGFloat { 2 }
private lazy var checkmarkIcon: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "check-compact"))
imageView.contentMode = .scaleAspectFit
imageView.tintColor = .white
return imageView
}()
private lazy var selectedView: UIView = {
let circleView = CircleView(frame: .init(origin: .zero, size: .square(MessageSelectionView.circleDiameter)))
circleView.addSubview(checkmarkIcon)
return circleView
}()
private lazy var unselectedView: UIView = {
let circleView = RingView()
circleView.lineWidth = MessageSelectionView.emptyCheckmarkStrokeLineWidth
return circleView
}()
func updateStyle(conversationStyle: ConversationStyle) {
AssertIsOnMainThread()
selectedView.backgroundColor = conversationStyle.chatColorValue.asChatUIElementTintColor()
unselectedView.tintColor = UIColor.Signal.tertiaryLabel
selectionIndicatorView.fillColor = conversationStyle.chatColorValue.asChatUIElementTintColor()
// Less transparent empty circle when there's a wallpaper and we're in light theme
// to improve legibility over darker wallpapers.
if
conversationStyle.isDarkThemeEnabled == false,
conversationStyle.hasWallpaper
{
selectionIndicatorView.unselectedListIndicatorColor = UIColor(rgbHex: 0x808080, alpha: 0.5)
} else {
selectionIndicatorView.unselectedListIndicatorColor = nil // reset to default
}
}
}

View File

@ -45,7 +45,8 @@ public class SelectionIndicatorView: UIView {
// MARK: Layout
private static let preferredSize: CGFloat = 24
// For `MessageSelectionView` to use.
public static let preferredSize: CGFloat = 24
private static let ringStrokeWidth: CGFloat = 2
@ -114,7 +115,7 @@ public class SelectionIndicatorView: UIView {
// MARK: Appearance
/// Color that fills the selection ring and is the background for checkmark image.
/// Color that fills the selection ring and is the background for checkmark image. Defaut is `UIColor.Signal.accent`.
public var fillColor: UIColor = .Signal.accent {
didSet {
selectedView.backgroundColor = fillColor
@ -122,10 +123,10 @@ public class SelectionIndicatorView: UIView {
}
private var effectiveFillColor: UIColor {
isEnabled ? fillColor : .Signal.tertiaryLabel
isEnabled ? fillColor : unselectedListIndicatorColor
}
/// Color for the ckeckmark image and outer ring for media-style indicators.
/// Color for the checkmark symbol and outer ring when `style` is `media`. Default is `white`.
public var strokeColor: UIColor = .white {
didSet {
checkmarkIcon.tintColor = strokeColor
@ -135,11 +136,25 @@ public class SelectionIndicatorView: UIView {
}
}
private var _unselectedColor: UIColor = .Signal.tertiaryLabel
/// Colors of the emty circle when `style` is `list`. Defaults to `UIColor.Signal.tertiaryLabel`.
public var unselectedListIndicatorColor: UIColor! {
get { _unselectedColor }
set {
owsAssertDebug(style == .list, "Invalid access")
_unselectedColor = newValue ?? .Signal.tertiaryLabel
if case .list = style {
innerRing.tintColor = unselectedListIndicatorColor
}
}
}
private lazy var innerRing: UIView = {
owsAssertDebug(style == .list, "Invalid access")
let ringView = RingView()
ringView.lineWidth = SelectionIndicatorView.ringStrokeWidth
ringView.tintColor = .Signal.tertiaryLabel
ringView.tintColor = unselectedListIndicatorColor
return ringView
}()