1
0
mirror of https://github.com/mastodon/mastodon-ios.git synced 2025-01-07 06:50:34 +01:00
mastodon-app-ufficiale-ipho.../MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift
CMK 6153839157
Release v1.3.0 (#347)
* 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>
2022-03-29 11:51:14 +02:00

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