mirror of
https://github.com/mastodon/mastodon-ios.git
synced 2025-01-07 06:50:34 +01:00
6153839157
* New translations app.json (Thai) * New translations app.json (Spanish) * New translations Localizable.stringsdict (Spanish) * New translations app.json (Thai) * New translations app.json (Thai) * feat: adapt the app to async & await. Update timeline UI * fix: update the Xcode version to fix the CI failure * fix: remove unavailable framework import * fix: project dependency issue * feat: add content warning for post spoiler * feat: add content warning for post media * chore: update version to 1.3.0 (92) * New translations app.json (French) * New translations Intents.strings (French) * New translations app.json (Thai) * feat: update report flow * feat: update setting scene UI * feat: update status content warning UI * feat: add notification gap fetcher * chore: update version to 1.3.0 (93) * feat: add video player for audio/video kind media * chore: update version to 1.3.0 (94) * fix: text strip wrong color in the Dark Mode issue * chore: remove spoiler toggle animation for table cell * fix: add missing shadow for compose publish button * fix: add missing margin for timeline with horizontal regular size class * fix: profile segmented controls missing margin issue * fix: the profile segmented control use wrong selection tint color under force light UI style issue * fix: add notification count clear logic back * fix: add missing home timeline bottom fetcher * fix: [WIP] add suggestion account scene back * New translations app.json (Kabyle) * New translations ios-infoPlist.json (Kabyle) * New translations Localizable.stringsdict (Kabyle) * New translations Intents.strings (Kabyle) * New translations Intents.stringsdict (Kabyle) * feat: make the home timeline readable for VoiceOver * chore: update version to 1.3.0 (95) * New translations app.json (French) * New translations Intents.strings (French) * New translations app.json (Kabyle) * New translations ios-infoPlist.json (Kabyle) * New translations Localizable.stringsdict (Kabyle) * New translations Intents.strings (Kabyle) * New translations Intents.stringsdict (Kabyle) * New translations Localizable.stringsdict (French) * New translations app.json (Kabyle) * New translations app.json (French) * chore: update action toolbar icons * fix: instal state missing issue * fix: follow push notification deep-link not works issue * fix: foreground notification not trigger tab bell icon update issue * feat: add notification timeline fetcher * feat: add content warning toggle button * chore: update version to 1.3.0 (96) * New translations app.json (Thai) * New translations app.json (Russian) * New translations app.json (Kurmanji (Kurdish)) * New translations app.json (Scottish Gaelic) * New translations app.json (Welsh) * New translations app.json (Hindi) * New translations app.json (Spanish, Argentina) * New translations app.json (Indonesian) * New translations app.json (Portuguese, Brazilian) * New translations app.json (English) * New translations app.json (Chinese Traditional) * New translations app.json (Chinese Simplified) * New translations app.json (Swedish) * New translations app.json (Portuguese) * New translations app.json (Dutch) * New translations app.json (Korean) * New translations app.json (Japanese) * New translations app.json (Basque) * New translations app.json (German) * New translations app.json (Danish) * New translations app.json (Catalan) * New translations app.json (Arabic) * New translations app.json (Spanish) * New translations app.json (Romanian) * New translations app.json (Kabyle) * New translations app.json (French) * New translations app.json (Swedish, Finland) * New translations app.json (Spanish, Argentina) * New translations app.json (Kurmanji (Kurdish)) * fix: notification i18n word typo * New translations app.json (Thai) * New translations app.json (Swedish) * New translations Localizable.stringsdict (Swedish) * New translations app.json (Swedish, Finland) * New translations app.json (Kurmanji (Kurdish)) * New translations app.json (Scottish Gaelic) * New translations app.json (Welsh) * New translations app.json (Hindi) * New translations app.json (Indonesian) * New translations app.json (Portuguese, Brazilian) * New translations app.json (English) * New translations app.json (Chinese Traditional) * New translations app.json (Chinese Simplified) * New translations app.json (Russian) * New translations app.json (Portuguese) * New translations app.json (Dutch) * New translations app.json (Korean) * New translations app.json (Japanese) * New translations app.json (Basque) * New translations app.json (German) * New translations app.json (Danish) * New translations app.json (Catalan) * New translations app.json (Arabic) * New translations app.json (Spanish) * New translations app.json (Romanian) * New translations app.json (Kabyle) * New translations app.json (French) * New translations Intents.strings (Swedish) * New translations app.json (Swedish) * New translations Localizable.stringsdict (Japanese) * New translations app.json (Thai) * New translations app.json (Thai) * New translations Localizable.stringsdict (Swedish) * New translations app.json (Kabyle) * New translations ios-infoPlist.json (Kabyle) * New translations app.json (Kabyle) * New translations app.json (French) * New translations app.json (French) * feat: restore scroll-to-top tap gesture for TabBar * feat: add cell height cache for user timeline * feat: display no results when profile field empty * New translations app.json (Chinese Traditional) * New translations app.json (Chinese Traditional) * New translations Intents.strings (Japanese) * feat: make status detail accessible * chore: restore the appearance settings * chore: update version to 1.3.0 (97) * New translations app.json (Kabyle) * New translations Intents.strings (Japanese) * New translations app.json (Swedish) * New translations app.json (Basque) * New translations app.json (Basque) * chore: add a11y hint for profile dashboard * feat: add media interaction for notification timeline * New translations app.json (Chinese Simplified) * New translations app.json (Chinese Simplified) * chore: update i18n strings * fix: setting switch use wrong tint color issue * chore: restore RTL layout for post content * chore: update profile relationship button UI * chore: update color panel * fix: post reblog header may display empty reblogger name issue * fix: wrong reply header redirect logic issue * feat: restore post filter supports * chore: update version to 1.3.0 (98) * chore: update post content sensitive style * fix: blurhash image not display during image loading issue * chore: update version to 1.3.0 (99) * feat: restore user recommend scene * chore: update badge tint color * feat: restore keyboard shortcut supports * chore: update version to 1.3.0 (100) * fix: relationship background use wrong color when force dark style * fix: player button icon not reset issue * chore: update version to 1.3.0 (101) * fix: profile relationship button fill the width on iPad issue * fix: inputAssistantItem duplicate setup issue * chore: update textView minimum height from 88 to 64 * chore: update version to 1.3.0 (102) * chore: update status timeline margin * chore: update sidebar background color * fix: split view column state after size class transition not stable issue * chore: update notification timeline margin * chore: update profile header and segmented bar margin * fix: profile segmented bar use wrong tint color when force Dark Mode issue * chore: update horizontal compact mode notification timeline margin looks like * chore: update version to 1.3.0 (103) * feat: dismiss image preview when tap empty area * chore: update version to 1.3.0 (104) * New translations app.json (Italian) * New translations ios-infoPlist.json (Italian) * New translations Localizable.stringsdict (Italian) * New translations Intents.strings (Italian) * New translations Intents.stringsdict (Italian) * New translations app.json (Kabyle) * New translations app.json (Kabyle) * New translations app.json (Kabyle) * New translations app.json (Kabyle) * New translations app.json (Kabyle) * New translations app.json (Kabyle) * New translations app.json (Japanese) * New translations app.json (Kabyle) * New translations app.json (Kabyle) * New translations app.json (Kabyle) * New translations app.json (Kabyle) * New translations app.json (Kabyle) * New translations app.json (Kabyle) * New translations app.json (Kabyle) * New translations app.json (Spanish) * New translations app.json (Kabyle) * New translations app.json (Kabyle) * New translations app.json (Kabyle) * New translations ios-infoPlist.json (Kabyle) * New translations Localizable.stringsdict (Kabyle) * New translations Localizable.stringsdict (Kabyle) * New translations Intents.strings (Kabyle) * New translations app.json (Kabyle) * New translations Intents.strings (Kabyle) * New translations Intents.stringsdict (Kabyle) * New translations app.json (Kabyle) * New translations app.json (Scottish Gaelic) * New translations app.json (Scottish Gaelic) * New translations app.json (Thai) * New translations app.json (Thai) * feat: add UITests for snapshots * feat: add snapshot UITest and document * New translations app.json (Thai) * feat: add notification snapshot * chore: add domain and update guide for the snapshot UITest * chore: use the first photo for compose snapshot * New translations app.json (Thai) * New translations app.json (German) * New translations app.json (German) * chore: update settings scene UI * chore: update i18n for open link words * chore: update i18n resources * fix: share extension not accept plaintext content issue. resolve #335 * chore: update version to 1.3.0 (105) * New translations app.json (Japanese) * New translations app.json (Japanese) * New translations app.json (Japanese) * feat: add onion domain ATS exception rule. resolve #338 * chore: update app version footer and i18n strings * chore: update version to 1.3.0 (106) * chore: update version to 1.3.0 (108) * Handle onboarding authentication errors in /api/v1/instance * New translations app.json (Kurmanji (Kurdish)) * New translations app.json (Kurmanji (Kurdish)) * chore: update Xcode schemes index * chore: update the snapshot documents and UITests * chore: update i18n resources. resolve #343 * chore: retain the API model semantic * fix: force LTR for some text fields. #318 * fix: textView break IME input issue. resolve #342 * chore: update version to 1.3.0 (109) * chore: update README * chore: fix typo * chore: add bug report template and contributing document Co-authored-by: Eugen Rochko <eugen@zeonfederated.com> Co-authored-by: Zac West <zacwest@gmail.com>
292 lines
12 KiB
Swift
292 lines
12 KiB
Swift
//
|
|
// ActionToolBarContainer.swift
|
|
// Mastodon
|
|
//
|
|
// Created by sxiaojian on 2021/2/1.
|
|
//
|
|
|
|
import os.log
|
|
import UIKit
|
|
import MastodonAsset
|
|
import MastodonLocalization
|
|
|
|
public protocol ActionToolbarContainerDelegate: AnyObject {
|
|
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, buttonDidPressed button: UIButton, action: ActionToolbarContainer.Action)
|
|
}
|
|
|
|
public final class ActionToolbarContainer: UIView {
|
|
|
|
let logger = Logger(subsystem: "ActionToolbarContainer", category: "Control")
|
|
|
|
static let replyImage = Asset.Communication.bubbleLeftAndBubbleRight.image.withRenderingMode(.alwaysTemplate)
|
|
static let reblogImage = Asset.Arrow.repeat.image.withRenderingMode(.alwaysTemplate)
|
|
static let starImage = Asset.ObjectsAndTools.star.image.withRenderingMode(.alwaysTemplate)
|
|
static let starFillImage = Asset.ObjectsAndTools.starFill.image.withRenderingMode(.alwaysTemplate)
|
|
static let shareImage = Asset.Communication.share.image.withRenderingMode(.alwaysTemplate)
|
|
|
|
public let replyButton = HighlightDimmableButton()
|
|
public let reblogButton = HighlightDimmableButton()
|
|
public let favoriteButton = HighlightDimmableButton()
|
|
public let shareButton = HighlightDimmableButton()
|
|
|
|
public weak var delegate: ActionToolbarContainerDelegate?
|
|
|
|
private let container = UIStackView()
|
|
private var style: Style?
|
|
|
|
public override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
_init()
|
|
}
|
|
|
|
public required init?(coder: NSCoder) {
|
|
super.init(coder: coder)
|
|
_init()
|
|
}
|
|
|
|
}
|
|
|
|
extension ActionToolbarContainer {
|
|
|
|
private func _init() {
|
|
container.translatesAutoresizingMaskIntoConstraints = false
|
|
addSubview(container)
|
|
NSLayoutConstraint.activate([
|
|
container.topAnchor.constraint(equalTo: topAnchor),
|
|
container.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
trailingAnchor.constraint(equalTo: container.trailingAnchor),
|
|
bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
|
])
|
|
|
|
replyButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
|
|
reblogButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
|
|
favoriteButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
|
|
shareButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
|
|
}
|
|
|
|
public func configure(for style: Style) {
|
|
guard needsConfigure(for: style) else {
|
|
return
|
|
}
|
|
|
|
self.style = style
|
|
container.arrangedSubviews.forEach { subview in
|
|
container.removeArrangedSubview(subview)
|
|
subview.removeFromSuperview()
|
|
}
|
|
|
|
let buttons = [replyButton, reblogButton, favoriteButton, shareButton]
|
|
buttons.forEach { button in
|
|
button.tintColor = Asset.Colors.Button.actionToolbar.color
|
|
button.titleLabel?.font = .monospacedDigitSystemFont(ofSize: 12, weight: .regular)
|
|
button.setTitle("", for: .normal)
|
|
button.setTitleColor(.secondaryLabel, for: .normal)
|
|
button.expandEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
|
|
button.setInsets(forContentPadding: .zero, imageTitlePadding: style.buttonTitleImagePadding)
|
|
}
|
|
// add more expand for menu button
|
|
shareButton.expandEdgeInsets = UIEdgeInsets(top: -10, left: -20, bottom: -10, right: -20)
|
|
|
|
replyButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.reply
|
|
reblogButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.reblog // needs update to follow state
|
|
favoriteButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.favorite // needs update to follow state
|
|
shareButton.accessibilityLabel = L10n.Common.Controls.Actions.share
|
|
|
|
switch style {
|
|
case .inline:
|
|
buttons.forEach { button in
|
|
button.contentHorizontalAlignment = .leading
|
|
}
|
|
replyButton.setImage(ActionToolbarContainer.replyImage, for: .normal)
|
|
reblogButton.setImage(ActionToolbarContainer.reblogImage, for: .normal)
|
|
favoriteButton.setImage(ActionToolbarContainer.starImage, for: .normal)
|
|
shareButton.setImage(ActionToolbarContainer.shareImage, for: .normal)
|
|
|
|
container.axis = .horizontal
|
|
container.distribution = .fill
|
|
|
|
replyButton.translatesAutoresizingMaskIntoConstraints = false
|
|
reblogButton.translatesAutoresizingMaskIntoConstraints = false
|
|
favoriteButton.translatesAutoresizingMaskIntoConstraints = false
|
|
shareButton.translatesAutoresizingMaskIntoConstraints = false
|
|
container.addArrangedSubview(replyButton)
|
|
container.addArrangedSubview(reblogButton)
|
|
container.addArrangedSubview(favoriteButton)
|
|
container.addArrangedSubview(shareButton)
|
|
NSLayoutConstraint.activate([
|
|
replyButton.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh),
|
|
replyButton.heightAnchor.constraint(equalTo: reblogButton.heightAnchor).priority(.defaultHigh),
|
|
replyButton.heightAnchor.constraint(equalTo: favoriteButton.heightAnchor).priority(.defaultHigh),
|
|
replyButton.heightAnchor.constraint(equalTo: shareButton.heightAnchor).priority(.defaultHigh),
|
|
replyButton.widthAnchor.constraint(equalTo: reblogButton.widthAnchor).priority(.defaultHigh),
|
|
replyButton.widthAnchor.constraint(equalTo: favoriteButton.widthAnchor).priority(.defaultHigh),
|
|
])
|
|
shareButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
|
shareButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
|
|
|
case .plain:
|
|
buttons.forEach { button in
|
|
button.contentHorizontalAlignment = .center
|
|
}
|
|
replyButton.setImage(ActionToolbarContainer.replyImage, for: .normal)
|
|
reblogButton.setImage(ActionToolbarContainer.reblogImage, for: .normal)
|
|
favoriteButton.setImage(ActionToolbarContainer.starImage, for: .normal)
|
|
|
|
container.axis = .horizontal
|
|
container.spacing = 8
|
|
container.distribution = .fillEqually
|
|
|
|
container.addArrangedSubview(replyButton)
|
|
container.addArrangedSubview(reblogButton)
|
|
container.addArrangedSubview(favoriteButton)
|
|
}
|
|
}
|
|
|
|
private func needsConfigure(for style: Style) -> Bool {
|
|
guard let oldStyle = self.style else { return true }
|
|
return oldStyle != style
|
|
}
|
|
|
|
}
|
|
|
|
extension ActionToolbarContainer {
|
|
|
|
public enum Action: String, CaseIterable {
|
|
case reply
|
|
case reblog
|
|
case like
|
|
case share
|
|
}
|
|
|
|
public enum Style {
|
|
case inline
|
|
case plain
|
|
|
|
var buttonTitleImagePadding: CGFloat {
|
|
switch self {
|
|
case .inline: return 4.0
|
|
case .plain: return 0
|
|
}
|
|
}
|
|
}
|
|
|
|
private func isReblogButtonHighlightStateDidChange(to isHighlight: Bool) {
|
|
let tintColor = isHighlight ? Asset.Colors.successGreen.color : Asset.Colors.Button.actionToolbar.color
|
|
reblogButton.tintColor = tintColor
|
|
reblogButton.setTitleColor(tintColor, for: .normal)
|
|
reblogButton.setTitleColor(tintColor, for: .highlighted)
|
|
}
|
|
|
|
private func isFavoriteButtonHighlightStateDidChange(to isHighlight: Bool) {
|
|
let tintColor = isHighlight ? Asset.Colors.systemOrange.color : Asset.Colors.Button.actionToolbar.color
|
|
favoriteButton.tintColor = tintColor
|
|
favoriteButton.setTitleColor(tintColor, for: .normal)
|
|
favoriteButton.setTitleColor(tintColor, for: .highlighted)
|
|
}
|
|
|
|
}
|
|
|
|
extension ActionToolbarContainer {
|
|
|
|
@objc private func buttonDidPressed(_ sender: UIButton) {
|
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
|
|
|
let _action: Action?
|
|
switch sender {
|
|
case replyButton: _action = .reply
|
|
case reblogButton: _action = .reblog
|
|
case favoriteButton: _action = .like
|
|
case shareButton: _action = .share
|
|
default: _action = nil
|
|
}
|
|
|
|
guard let action = _action else {
|
|
assertionFailure()
|
|
return
|
|
}
|
|
|
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(action.rawValue) button pressed")
|
|
delegate?.actionToolbarContainer(self, buttonDidPressed: sender, action: action)
|
|
}
|
|
|
|
}
|
|
|
|
extension ActionToolbarContainer {
|
|
|
|
public func configureReply(count: Int, isEnabled: Bool) {
|
|
let title = ActionToolbarContainer.title(from: count)
|
|
replyButton.setTitle(title, for: .normal)
|
|
replyButton.accessibilityLabel = "\(count) reply" // TODO: i18n
|
|
}
|
|
|
|
public func configureReblog(count: Int, isEnabled: Bool, isHighlighted: Bool) {
|
|
let title = ActionToolbarContainer.title(from: count)
|
|
reblogButton.setTitle(title, for: .normal)
|
|
reblogButton.isEnabled = isEnabled
|
|
reblogButton.setImage(ActionToolbarContainer.reblogImage, for: .normal)
|
|
let tintColor = isHighlighted ? Asset.Colors.successGreen.color : Asset.Colors.Button.actionToolbar.color
|
|
reblogButton.tintColor = tintColor
|
|
reblogButton.setTitleColor(tintColor, for: .normal)
|
|
reblogButton.setTitleColor(tintColor, for: .highlighted)
|
|
|
|
if isHighlighted {
|
|
reblogButton.accessibilityTraits.insert(.selected)
|
|
} else {
|
|
reblogButton.accessibilityTraits.remove(.selected)
|
|
}
|
|
reblogButton.accessibilityLabel = L10n.Plural.Count.reblog(count)
|
|
}
|
|
|
|
public func configureFavorite(count: Int, isEnabled: Bool, isHighlighted: Bool) {
|
|
let title = ActionToolbarContainer.title(from: count)
|
|
favoriteButton.setTitle(title, for: .normal)
|
|
favoriteButton.isEnabled = isEnabled
|
|
let image = isHighlighted ? ActionToolbarContainer.starFillImage : ActionToolbarContainer.starImage
|
|
favoriteButton.setImage(image, for: .normal)
|
|
let tintColor = isHighlighted ? Asset.Colors.systemOrange.color : Asset.Colors.Button.actionToolbar.color
|
|
favoriteButton.tintColor = tintColor
|
|
favoriteButton.setTitleColor(tintColor, for: .normal)
|
|
favoriteButton.setTitleColor(tintColor, for: .highlighted)
|
|
|
|
if isHighlighted {
|
|
favoriteButton.accessibilityTraits.insert(.selected)
|
|
} else {
|
|
favoriteButton.accessibilityTraits.remove(.selected)
|
|
}
|
|
favoriteButton.accessibilityLabel = L10n.Plural.Count.favorite(count)
|
|
}
|
|
|
|
}
|
|
|
|
extension ActionToolbarContainer {
|
|
private static func title(from number: Int?) -> String {
|
|
guard let number = number, number > 0 else { return "" }
|
|
return String(number)
|
|
}
|
|
}
|
|
|
|
extension ActionToolbarContainer {
|
|
public override var accessibilityElements: [Any]? {
|
|
get { [replyButton, reblogButton, favoriteButton, shareButton] }
|
|
set { }
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
import SwiftUI
|
|
|
|
struct ActionToolbarContainer_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
Group {
|
|
UIViewPreview(width: 300) {
|
|
let toolbar = ActionToolbarContainer()
|
|
toolbar.configure(for: .inline)
|
|
return toolbar
|
|
}
|
|
.previewLayout(.fixed(width: 300, height: 44))
|
|
.previewDisplayName("Inline")
|
|
}
|
|
}
|
|
}
|
|
#endif
|