From 5c3b8ccdfe5c4a61f04633e77f197ae00b15b00c Mon Sep 17 00:00:00 2001 From: Kiryl Kaveryn Date: Sat, 23 Dec 2023 18:29:05 +0400 Subject: [PATCH] [ios] refactor Toast class and improve toast message style 1. update style: bigger fonts and insets 2. update background blur 3. get rid of MWM prefix 4. replace the timer with the simplier dispatch async after. In this case there is no needed to create a timer for each toasts message just to add a timeout 5. reorder Toast class methods 6. replace the instance `show` method with a `static show`. Because there non needed to call show every time. We do not have stored toast that will be showed in different places thane created. Signed-off-by: Kiryl Kaveryn Signed-off-by: Kiryl Kaveryn --- .../Classes/CustomAlert/Toast/Toast.swift | 133 ++++++++++-------- .../SideButtons/MWMSideButtons.mm | 2 +- .../MWMTrafficButtonViewController.mm | 12 +- iphone/Maps/Core/Theme/GlobalStyleSheet.swift | 18 ++- .../TrackRecorder/TrackRecordingManager.swift | 2 +- .../Maps/UI/Editor/MWMEditorViewController.mm | 2 +- .../AboutController/AboutController.swift | 2 +- .../AboutController/Views/DonationView.swift | 2 +- .../UI/PlacePage/PlacePageInteractor.swift | 2 +- .../UI/Settings/MWMSettingsViewController.mm | 4 +- 10 files changed, 99 insertions(+), 80 deletions(-) diff --git a/iphone/Maps/Classes/CustomAlert/Toast/Toast.swift b/iphone/Maps/Classes/CustomAlert/Toast/Toast.swift index 44ebd057eb..e403105e27 100644 --- a/iphone/Maps/Classes/CustomAlert/Toast/Toast.swift +++ b/iphone/Maps/Classes/CustomAlert/Toast/Toast.swift @@ -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 }) } diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/SideButtons/MWMSideButtons.mm b/iphone/Maps/Classes/CustomViews/MapViewControls/SideButtons/MWMSideButtons.mm index 1eab6ad453..f45a33d666 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/SideButtons/MWMSideButtons.mm +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/SideButtons/MWMSideButtons.mm @@ -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]; } diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/TrafficButton/MWMTrafficButtonViewController.mm b/iphone/Maps/Classes/CustomViews/MapViewControls/TrafficButton/MWMTrafficButtonViewController.mm index 007a7c9949..65f28156b2 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/TrafficButton/MWMTrafficButtonViewController.mm +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/TrafficButton/MWMTrafficButtonViewController.mm @@ -65,7 +65,7 @@ NSArray *imagesWithName(NSString *name) { - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; - [MWMToast hideAll]; + [Toast hideAll]; } - (void)configLayout { @@ -115,7 +115,7 @@ NSArray *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 *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 *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 *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]]; diff --git a/iphone/Maps/Core/Theme/GlobalStyleSheet.swift b/iphone/Maps/Core/Theme/GlobalStyleSheet.swift index 60590d985b..3799fa0627 100644 --- a/iphone/Maps/Core/Theme/GlobalStyleSheet.swift +++ b/iphone/Maps/Core/Theme/GlobalStyleSheet.swift @@ -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 + } } } } diff --git a/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift b/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift index 5012b2ed65..30ab1121ce 100644 --- a/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift +++ b/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift @@ -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 } diff --git a/iphone/Maps/UI/Editor/MWMEditorViewController.mm b/iphone/Maps/UI/Editor/MWMEditorViewController.mm index 8964ee0976..c2a78e2d09 100644 --- a/iphone/Maps/UI/Editor/MWMEditorViewController.mm +++ b/iphone/Maps/UI/Editor/MWMEditorViewController.mm @@ -256,7 +256,7 @@ void registerCellsForTableView(std::vector const & cells, UITab - (void)showNotesQueuedToast { - [[MWMToast toastWithText:L(@"editor_edits_sent_message")] show]; + [Toast showWithText:L(@"editor_edits_sent_message")]; } #pragma mark - Headers diff --git a/iphone/Maps/UI/Help/AboutController/AboutController.swift b/iphone/Maps/UI/Help/AboutController/AboutController.swift index a0e4c07945..a010aa6abb 100644 --- a/iphone/Maps/UI/Help/AboutController/AboutController.swift +++ b/iphone/Maps/UI/Help/AboutController/AboutController.swift @@ -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) } } diff --git a/iphone/Maps/UI/Help/AboutController/Views/DonationView.swift b/iphone/Maps/UI/Help/AboutController/Views/DonationView.swift index ca2084c2ac..4fc72d3d94 100644 --- a/iphone/Maps/UI/Help/AboutController/Views/DonationView.swift +++ b/iphone/Maps/UI/Help/AboutController/Views/DonationView.swift @@ -59,7 +59,7 @@ final class DonationView: UIView { } } -private extension NSLayoutConstraint { +extension NSLayoutConstraint { func withPriority(_ priority: UILayoutPriority) -> NSLayoutConstraint { self.priority = priority return self diff --git a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift index bb2e8161b3..2919c85e17 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift @@ -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) { diff --git a/iphone/Maps/UI/Settings/MWMSettingsViewController.mm b/iphone/Maps/UI/Settings/MWMSettingsViewController.mm index d4cf08a266..9a6e14e47f 100644 --- a/iphone/Maps/UI/Settings/MWMSettingsViewController.mm +++ b/iphone/Maps/UI/Settings/MWMSettingsViewController.mm @@ -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; } -- 2.45.3