This commit is contained in:
Kiryl Kaveryn 2025-03-13 15:18:05 +01:00 committed by GitHub
commit 4de9a6a640
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 99 additions and 80 deletions

View file

@ -1,101 +1,112 @@
@objc(MWMToast)
@objc
final class Toast: NSObject {
@objc(MWMToastAlignment)
@objc
enum Alignment: Int {
case bottom
case top
}
private var blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
private var timer: Timer?
private enum Constants {
static let presentationDuration: TimeInterval = 3
static let animationDuration: TimeInterval = kDefaultAnimationDuration
static let bottomOffset: CGFloat = 63
static let topOffset: CGFloat = 50
static let horizontalOffset: CGFloat = 16
static let labelOffsets = UIEdgeInsets(top: 10, left: 14, bottom: -10, right: -14)
static let maxWidth: CGFloat = 400
}
private static var toasts: [Toast] = []
private var blurView: UIVisualEffectView
@objc static func toast(withText text: String) -> Toast {
let toast = Toast(text)
toasts.append(toast)
return toast
}
@objc static func hideAll() {
toasts.forEach { $0.hide() }
}
private init(_ text: String) {
blurView.layer.setCorner(radius: 8)
blurView.clipsToBounds = true
blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
blurView.setStyle(.toastBackground)
blurView.isUserInteractionEnabled = false
blurView.alpha = 0
blurView.translatesAutoresizingMaskIntoConstraints = false
let label = UILabel()
label.text = text
label.textAlignment = .center
label.setStyle(.toastLabel)
label.numberOfLines = 0
label.font = .regular14()
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
label.setContentHuggingPriority(.defaultLow, for: .horizontal)
blurView.contentView.addSubview(label)
blurView.isUserInteractionEnabled = false
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: blurView.contentView.leadingAnchor, constant: 8),
label.trailingAnchor.constraint(equalTo: blurView.contentView.trailingAnchor, constant: -8),
label.topAnchor.constraint(equalTo: blurView.contentView.topAnchor, constant: 8),
label.bottomAnchor.constraint(equalTo: blurView.contentView.bottomAnchor, constant: -8)
label.leadingAnchor.constraint(equalTo: blurView.contentView.leadingAnchor, constant: Constants.labelOffsets.left),
label.trailingAnchor.constraint(equalTo: blurView.contentView.trailingAnchor, constant: Constants.labelOffsets.right),
label.topAnchor.constraint(equalTo: blurView.contentView.topAnchor, constant: Constants.labelOffsets.top),
label.bottomAnchor.constraint(equalTo: blurView.contentView.bottomAnchor, constant: Constants.labelOffsets.bottom)
])
}
deinit {
timer?.invalidate()
// MARK: - Public methods
@objc
static func show(withText text: String) {
show(withText: text, alignment: .bottom)
}
@objc func show() {
show(in: UIApplication.shared.keyWindow, alignment: .bottom)
@objc
static func show(withText text: String, alignment: Alignment) {
show(withText: text, alignment: alignment, pinToSafeArea: true)
}
@objc func show(withAlignment alignment: Alignment, pinToSafeArea: Bool = true) {
show(in: UIApplication.shared.keyWindow, alignment: alignment, pinToSafeArea: pinToSafeArea)
@objc
static func show(withText text: String, alignment: Alignment, pinToSafeArea: Bool) {
let toast = Toast(text)
toasts.append(toast)
toast.show(withAlignment: alignment, pinToSafeArea: pinToSafeArea)
}
@objc func show(in view: UIView?, alignment: Alignment, pinToSafeArea: Bool = true) {
guard let view = view else { return }
blurView.translatesAutoresizingMaskIntoConstraints = false
@objc
static func hideAll() {
toasts.forEach { $0.hide() }
}
// MARK: - Private methods
private func show(withAlignment alignment: Alignment, pinToSafeArea: Bool) {
Self.hideAll()
guard let view = UIApplication.shared.keyWindow else { return }
view.addSubview(blurView)
let leadingConstraint = blurView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 16)
let trailingConstraint = blurView.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -16)
let leadingConstraint = blurView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: Constants.horizontalOffset)
let trailingConstraint = blurView.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -Constants.horizontalOffset)
let maxWidthConstraint = blurView.widthAnchor.constraint(equalToConstant: Constants.maxWidth).withPriority(.defaultLow)
let verticalConstraint: NSLayoutConstraint
switch alignment {
case .bottom:
verticalConstraint = blurView.bottomAnchor.constraint(equalTo: pinToSafeArea ? view.safeAreaLayoutGuide.bottomAnchor : view.bottomAnchor,
constant: -Constants.bottomOffset)
case .top:
verticalConstraint = blurView.topAnchor.constraint(equalTo: pinToSafeArea ? view.safeAreaLayoutGuide.topAnchor : view.topAnchor,
constant: Constants.topOffset)
}
NSLayoutConstraint.activate([
leadingConstraint,
trailingConstraint
])
let topConstraint: NSLayoutConstraint
if alignment == .bottom {
topConstraint = blurView.bottomAnchor.constraint(equalTo: pinToSafeArea ? view.safeAreaLayoutGuide.bottomAnchor : view.bottomAnchor, constant: -63)
} else {
topConstraint = blurView.topAnchor.constraint(equalTo: pinToSafeArea ? view.safeAreaLayoutGuide.topAnchor : view.topAnchor, constant: 50)
}
NSLayoutConstraint.activate([
topConstraint,
trailingConstraint,
maxWidthConstraint,
verticalConstraint,
blurView.centerXAnchor.constraint(equalTo: pinToSafeArea ? view.safeAreaLayoutGuide.centerXAnchor : view.centerXAnchor)
])
UIView.animate(withDuration: kDefaultAnimationDuration) {
UIView.animate(withDuration: Constants.animationDuration, animations: {
self.blurView.alpha = 1
}
timer = Timer.scheduledTimer(timeInterval: 3,
target: self,
selector: #selector(hide),
userInfo: nil,
repeats: false)
} , completion: { _ in
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.presentationDuration) {
self.hide()
}
})
}
@objc func hide() {
timer?.invalidate()
private func hide() {
if self.blurView.superview != nil {
UIView.animate(withDuration: kDefaultAnimationDuration,
UIView.animate(withDuration: Constants.animationDuration,
animations: { self.blurView.alpha = 0 }) { [self] _ in
self.blurView.removeFromSuperview()
Self.toasts.removeAll(where: { $0 === self }) }

View file

@ -143,7 +143,7 @@ NSString * const kUDDidShowLongTapToShowSideButtonsToast = @"kUDDidShowLongTapTo
- (void)setHidden:(BOOL)hidden
{
if (!self.hidden && hidden)
[[MWMToast toastWithText:L(@"long_tap_toast")] show];
[Toast showWithText:L(@"long_tap_toast")];
return [self.sideView setHidden:hidden animated:YES];
}

View file

@ -65,7 +65,7 @@ NSArray<UIImage *> *imagesWithName(NSString *name) {
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[MWMToast hideAll];
[Toast hideAll];
}
- (void)configLayout {
@ -115,7 +115,7 @@ NSArray<UIImage *> *imagesWithName(NSString *name) {
break;
case MWMMapOverlayTrafficStateNoData:
btn.imageName = @"btn_traffic_on";
[[MWMToast toastWithText:L(@"traffic_data_unavailable")] show];
[Toast showWithText:L(@"traffic_data_unavailable")];
break;
case MWMMapOverlayTrafficStateNetworkError:
[MWMMapOverlayManager setTrafficEnabled:NO];
@ -123,11 +123,11 @@ NSArray<UIImage *> *imagesWithName(NSString *name) {
break;
case MWMMapOverlayTrafficStateExpiredData:
btn.imageName = @"btn_traffic_outdated";
[[MWMToast toastWithText:L(@"traffic_update_maps_text")] show];
[Toast showWithText:L(@"traffic_update_maps_text")];
break;
case MWMMapOverlayTrafficStateExpiredApp:
btn.imageName = @"btn_traffic_outdated";
[[MWMToast toastWithText:L(@"traffic_update_app_message")] show];
[Toast showWithText:L(@"traffic_update_app_message")];
break;
}
}
@ -138,7 +138,7 @@ NSArray<UIImage *> *imagesWithName(NSString *name) {
break;
case MWMMapOverlayIsolinesStateEnabled:
if (![MWMMapOverlayManager isolinesVisible])
[[MWMToast toastWithText:L(@"isolines_toast_zooms_1_10")] show];
[Toast showWithText:L(@"isolines_toast_zooms_1_10")];
break;
case MWMMapOverlayIsolinesStateExpiredData:
[MWMAlertViewController.activeAlertController presentInfoAlert:L(@"isolines_activation_error_dialog")];
@ -162,7 +162,7 @@ NSArray<UIImage *> *imagesWithName(NSString *name) {
} else if ([MWMMapOverlayManager transitEnabled]) {
btn.imageName = @"btn_subway_on";
if ([MWMMapOverlayManager transitState] == MWMMapOverlayTransitStateNoData)
[[MWMToast toastWithText:L(@"subway_data_unavailable")] show];
[Toast showWithText:L(@"subway_data_unavailable")];
} else if ([MWMMapOverlayManager isoLinesEnabled]) {
btn.imageName = @"btn_isoMap_on";
[self handleIsolinesState:[MWMMapOverlayManager isolinesState]];

View file

@ -23,7 +23,6 @@ enum GlobalStyleSheet: String, CaseIterable {
case trackRecordingWidgetButton = "TrackRecordingWidgetButton"
case blackOpaqueBackground = "BlackOpaqueBackground"
case blueBackground = "BlueBackground"
case toastBackground = "ToastBackground"
case fadeBackground = "FadeBackground"
case errorBackground = "ErrorBackground"
case blackStatusBarBackground = "BlackStatusBarBackground"
@ -59,6 +58,8 @@ enum GlobalStyleSheet: String, CaseIterable {
case white = "MWMWhite"
case datePickerView = "DatePickerView"
case valueStepperView = "ValueStepperView"
case toastBackground
case toastLabel
}
extension GlobalStyleSheet: IStyleSheet {
@ -194,10 +195,6 @@ extension GlobalStyleSheet: IStyleSheet {
return .add { s in
s.backgroundColor = colors.linkBlue
}
case .toastBackground:
return .add { s in
s.backgroundColor = colors.toastBackground
}
case .fadeBackground:
return .add { s in
s.backgroundColor = colors.fadeBackground
@ -429,6 +426,17 @@ extension GlobalStyleSheet: IStyleSheet {
s.fontColor = colors.blackPrimaryText
s.coloring = MWMButtonColoring.blue
}
case .toastBackground:
return .add { s in
s.cornerRadius = 12
s.clip = true
}
case .toastLabel:
return .add { s in
s.font = fonts.regular16
s.fontColor = colors.whitePrimaryText
s.textAlignment = .center
}
}
}
}

View file

@ -138,7 +138,7 @@ final class TrackRecordingManager: NSObject {
private func stop(completion: (CompletionHandler)? = nil) {
guard !trackRecorder.isTrackRecordingEmpty() else {
Toast.toast(withText: L("track_recording_toast_nothing_to_save")).show()
Toast.show(withText: L("track_recording_toast_nothing_to_save"))
stopRecording(.withoutSaving, completion: completion)
return
}

View file

@ -256,7 +256,7 @@ void registerCellsForTableView(std::vector<MWMEditorCellID> const & cells, UITab
- (void)showNotesQueuedToast
{
[[MWMToast toastWithText:L(@"editor_edits_sent_message")] show];
[Toast showWithText:L(@"editor_edits_sent_message")];
}
#pragma mark - Headers

View file

@ -346,7 +346,7 @@ private extension AboutController {
UIPasteboard.general.string = content
let message = String(format: L("copied_to_clipboard"), content)
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
Toast.toast(withText: message).show(withAlignment: .bottom, pinToSafeArea: false)
Toast.show(withText: message, alignment: .bottom, pinToSafeArea: false)
}
}

View file

@ -59,7 +59,7 @@ final class DonationView: UIView {
}
}
private extension NSLayoutConstraint {
extension NSLayoutConstraint {
func withPriority(_ priority: UILayoutPriority) -> NSLayoutConstraint {
self.priority = priority
return self

View file

@ -129,7 +129,7 @@ extension PlacePageInteractor: PlacePageInfoViewControllerDelegate {
UIPasteboard.general.string = content
let message = String(format: L("copied_to_clipboard"), content)
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
Toast.toast(withText: message).show(withAlignment: .bottom)
Toast.show(withText: message, alignment: .bottom)
}
func didPressOpenInApp(from sourceView: UIView) {

View file

@ -254,12 +254,12 @@ static NSString * const kUDDidShowICloudSynchronizationEnablingAlert = @"kUDDidS
break;
}
case MWMBookmarksShareStatusEmptyCategory:
[[MWMToast toastWithText:L(@"bookmarks_error_title_share_empty")] show];
[Toast showWithText:L(@"bookmarks_error_title_share_empty")];
isEnabled(NO);
break;
case MWMBookmarksShareStatusArchiveError:
case MWMBookmarksShareStatusFileError:
[[MWMToast toastWithText:L(@"dialog_routing_system_error")] show];
[Toast showWithText:L(@"dialog_routing_system_error")];
isEnabled(NO);
break;
}