Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
|
|
@ -0,0 +1,59 @@
|
|||
final class CircleImageButton: UIButton {
|
||||
|
||||
private static let expandedTappableAreaInsets = UIEdgeInsets(top: -5, left: -5, bottom: -5, right: -5)
|
||||
|
||||
private let circleImageView = UIImageView()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupView()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
setupView()
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
|
||||
circleImageView.applyTheme()
|
||||
}
|
||||
}
|
||||
|
||||
private func setupView() {
|
||||
backgroundColor = .clear
|
||||
circleImageView.setStyle(.ppHeaderCircleIcon)
|
||||
circleImageView.contentMode = .scaleAspectFill
|
||||
circleImageView.clipsToBounds = true
|
||||
circleImageView.isUserInteractionEnabled = false
|
||||
circleImageView.layer.masksToBounds = true
|
||||
circleImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(circleImageView)
|
||||
|
||||
let aspectRatioConstraint = circleImageView.widthAnchor.constraint(equalTo: circleImageView.heightAnchor)
|
||||
aspectRatioConstraint.priority = .defaultHigh
|
||||
NSLayoutConstraint.activate([
|
||||
circleImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
circleImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
circleImageView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor),
|
||||
circleImageView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor),
|
||||
aspectRatioConstraint
|
||||
])
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
circleImageView.layer.cornerRadius = circleImageView.bounds.width / 2.0
|
||||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
let expandedBounds = bounds.inset(by: Self.expandedTappableAreaInsets)
|
||||
return expandedBounds.contains(point)
|
||||
}
|
||||
|
||||
func setImage(_ image: UIImage?) {
|
||||
circleImageView.image = image
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
class PlacePageHeaderBuilder {
|
||||
static func build(data: PlacePageData,
|
||||
delegate: PlacePageHeaderViewControllerDelegate?,
|
||||
headerType: PlacePageHeaderPresenter.HeaderType) -> PlacePageHeaderViewController {
|
||||
let storyboard = UIStoryboard.instance(.placePage)
|
||||
let viewController = storyboard.instantiateViewController(ofType: PlacePageHeaderViewController.self);
|
||||
let presenter = PlacePageHeaderPresenter(view: viewController,
|
||||
placePagePreviewData: data.previewData,
|
||||
objectType: data.objectType,
|
||||
delegate: delegate,
|
||||
headerType: headerType)
|
||||
|
||||
viewController.presenter = presenter
|
||||
|
||||
return viewController
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
protocol PlacePageHeaderPresenterProtocol: AnyObject {
|
||||
var objectType: PlacePageObjectType { get }
|
||||
|
||||
func configure()
|
||||
func onClosePress()
|
||||
func onExpandPress()
|
||||
func onShareButtonPress(from sourceView: UIView)
|
||||
func onExportTrackButtonPress(_ type: KmlFileType, from sourceView: UIView)
|
||||
}
|
||||
|
||||
protocol PlacePageHeaderViewControllerDelegate: AnyObject {
|
||||
func previewDidPressClose()
|
||||
func previewDidPressExpand()
|
||||
func previewDidPressShare(from sourceView: UIView)
|
||||
func previewDidPressExportTrack(_ type: KmlFileType, from sourceView: UIView)
|
||||
}
|
||||
|
||||
class PlacePageHeaderPresenter {
|
||||
enum HeaderType {
|
||||
case flexible
|
||||
case fixed
|
||||
}
|
||||
|
||||
private weak var view: PlacePageHeaderViewProtocol?
|
||||
private let placePagePreviewData: PlacePagePreviewData
|
||||
let objectType: PlacePageObjectType
|
||||
private weak var delegate: PlacePageHeaderViewControllerDelegate?
|
||||
private let headerType: HeaderType
|
||||
|
||||
init(view: PlacePageHeaderViewProtocol,
|
||||
placePagePreviewData: PlacePagePreviewData,
|
||||
objectType: PlacePageObjectType,
|
||||
delegate: PlacePageHeaderViewControllerDelegate?,
|
||||
headerType: HeaderType) {
|
||||
self.view = view
|
||||
self.delegate = delegate
|
||||
self.placePagePreviewData = placePagePreviewData
|
||||
self.objectType = objectType
|
||||
self.headerType = headerType
|
||||
}
|
||||
}
|
||||
|
||||
extension PlacePageHeaderPresenter: PlacePageHeaderPresenterProtocol {
|
||||
func configure() {
|
||||
view?.setTitle(placePagePreviewData.title, secondaryTitle: placePagePreviewData.secondaryTitle, branch: placePagePreviewData.branch)
|
||||
switch headerType {
|
||||
case .flexible:
|
||||
view?.isExpandViewHidden = false
|
||||
view?.isShadowViewHidden = true
|
||||
case .fixed:
|
||||
view?.isExpandViewHidden = true
|
||||
view?.isShadowViewHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
func onClosePress() {
|
||||
delegate?.previewDidPressClose()
|
||||
}
|
||||
|
||||
func onExpandPress() {
|
||||
delegate?.previewDidPressExpand()
|
||||
}
|
||||
|
||||
func onShareButtonPress(from sourceView: UIView) {
|
||||
delegate?.previewDidPressShare(from: sourceView)
|
||||
}
|
||||
|
||||
func onExportTrackButtonPress(_ type: KmlFileType, from sourceView: UIView) {
|
||||
delegate?.previewDidPressExportTrack(type, from: sourceView)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
class PlacePageHeaderView: UIView {
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
for subview in subviews {
|
||||
if !subview.isHidden && subview.isUserInteractionEnabled && subview.point(inside: convert(point, to: subview), with: event) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
protocol PlacePageHeaderViewProtocol: AnyObject {
|
||||
var presenter: PlacePageHeaderPresenterProtocol? { get set }
|
||||
var isExpandViewHidden: Bool { get set }
|
||||
var isShadowViewHidden: Bool { get set }
|
||||
|
||||
func setTitle(_ title: String?, secondaryTitle: String?, branch: String?)
|
||||
func showShareTrackMenu()
|
||||
}
|
||||
|
||||
class PlacePageHeaderViewController: UIViewController {
|
||||
var presenter: PlacePageHeaderPresenterProtocol?
|
||||
|
||||
@IBOutlet private var headerView: PlacePageHeaderView!
|
||||
@IBOutlet private var titleLabel: UILabel?
|
||||
@IBOutlet private var expandView: UIView!
|
||||
@IBOutlet private var shadowView: UIView!
|
||||
@IBOutlet private var grabberView: UIView!
|
||||
|
||||
@IBOutlet weak var closeButton: CircleImageButton!
|
||||
@IBOutlet weak var shareButton: CircleImageButton!
|
||||
|
||||
private var titleText: String?
|
||||
private var secondaryText: String?
|
||||
private var branchText: String?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
presenter?.configure()
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(onExpandPressed(sender:)))
|
||||
expandView.addGestureRecognizer(tap)
|
||||
headerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
iPadSpecific { [weak self] in
|
||||
self?.grabberView.isHidden = true
|
||||
}
|
||||
closeButton.setImage(UIImage(named: "ic_close")!)
|
||||
shareButton.setImage(UIImage(named: "ic_share")!)
|
||||
|
||||
if presenter?.objectType == .track {
|
||||
configureTrackSharingMenu()
|
||||
}
|
||||
|
||||
let interaction = UIContextMenuInteraction(delegate: self)
|
||||
titleLabel?.addInteraction(interaction)
|
||||
}
|
||||
|
||||
@objc func onExpandPressed(sender: UITapGestureRecognizer) {
|
||||
presenter?.onExpandPress()
|
||||
}
|
||||
|
||||
@IBAction private func onCloseButtonPressed(_ sender: Any) {
|
||||
presenter?.onClosePress()
|
||||
}
|
||||
|
||||
@IBAction func onShareButtonPressed(_ sender: Any) {
|
||||
presenter?.onShareButtonPress(from: shareButton)
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
guard traitCollection.userInterfaceStyle != previousTraitCollection?.userInterfaceStyle else { return }
|
||||
setTitle(titleText, secondaryTitle: secondaryText, branch: branchText)
|
||||
}
|
||||
}
|
||||
|
||||
extension PlacePageHeaderViewController: PlacePageHeaderViewProtocol {
|
||||
var isExpandViewHidden: Bool {
|
||||
get {
|
||||
expandView.isHidden
|
||||
}
|
||||
set {
|
||||
expandView.isHidden = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var isShadowViewHidden: Bool {
|
||||
get {
|
||||
shadowView.isHidden
|
||||
}
|
||||
set {
|
||||
shadowView.isHidden = newValue
|
||||
}
|
||||
}
|
||||
|
||||
func setTitle(_ title: String?, secondaryTitle: String?, branch: String? = nil) {
|
||||
titleText = title
|
||||
secondaryText = secondaryTitle
|
||||
branchText = branch
|
||||
|
||||
// XCode 13 is not smart enough to detect that title is used below, and requires explicit unwrapped variable.
|
||||
guard let unwrappedTitle = title else {
|
||||
titleLabel?.attributedText = nil
|
||||
return
|
||||
}
|
||||
|
||||
let titleAttributes: [NSAttributedString.Key: Any] = [
|
||||
.font: StyleManager.shared.theme!.fonts.semibold20,
|
||||
.foregroundColor: UIColor.blackPrimaryText()
|
||||
]
|
||||
|
||||
let attributedText = NSMutableAttributedString(string: unwrappedTitle, attributes: titleAttributes)
|
||||
|
||||
// Add branch with thinner font weight if present and not already in title
|
||||
if let branch = branch, !branch.isEmpty, !unwrappedTitle.contains(branch) {
|
||||
let branchAttributes: [NSAttributedString.Key: Any] = [
|
||||
.font: StyleManager.shared.theme!.fonts.regular20,
|
||||
.foregroundColor: UIColor.blackPrimaryText()
|
||||
]
|
||||
attributedText.append(NSAttributedString(string: " \(branch)", attributes: branchAttributes))
|
||||
}
|
||||
|
||||
guard let unwrappedSecondaryTitle = secondaryTitle else {
|
||||
titleLabel?.attributedText = attributedText
|
||||
return
|
||||
}
|
||||
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
paragraphStyle.paragraphSpacingBefore = 2
|
||||
let secondaryTitleAttributes: [NSAttributedString.Key: Any] = [
|
||||
.font: StyleManager.shared.theme!.fonts.medium16,
|
||||
.foregroundColor: UIColor.blackPrimaryText(),
|
||||
.paragraphStyle: paragraphStyle
|
||||
]
|
||||
|
||||
attributedText.append(NSAttributedString(string: "\n" + unwrappedSecondaryTitle, attributes: secondaryTitleAttributes))
|
||||
titleLabel?.attributedText = attributedText
|
||||
}
|
||||
|
||||
func showShareTrackMenu() {
|
||||
// The menu will be shown by the shareButton itself
|
||||
}
|
||||
|
||||
private func configureTrackSharingMenu() {
|
||||
let menu = UIMenu(title: "", image: nil, children: [
|
||||
UIAction(title: L("export_file"), image: nil, handler: { [weak self] _ in
|
||||
guard let self else { return }
|
||||
self.presenter?.onExportTrackButtonPress(.text, from: self.shareButton)
|
||||
}),
|
||||
UIAction(title: L("export_file_gpx"), image: nil, handler: { [weak self] _ in
|
||||
guard let self else { return }
|
||||
self.presenter?.onExportTrackButtonPress(.gpx, from: self.shareButton)
|
||||
}),
|
||||
])
|
||||
shareButton.menu = menu
|
||||
shareButton.showsMenuAsPrimaryAction = true
|
||||
}
|
||||
}
|
||||
|
||||
extension PlacePageHeaderViewController: UIContextMenuInteractionDelegate {
|
||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
||||
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { suggestedActions in
|
||||
let copyAction = UIAction(title: L("copy_to_clipboard"), image: UIImage(systemName: "document.on.clipboard")) { action in
|
||||
UIPasteboard.general.string = self.titleLabel?.text
|
||||
}
|
||||
return UIMenu(title: "", children: [copyAction])
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue