[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 <kirylkaveryn@gmail.com>

Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
This commit is contained in:
Kiryl Kaveryn
2023-12-23 18:29:05 +04:00
committed by Konstantin Pastbin
parent 72cc4bbd50
commit 103d660603
10 changed files with 99 additions and 80 deletions

View File

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

View File

@@ -141,7 +141,7 @@ NSString * const kUDDidShowLongTapToShowSideButtonsToast = @"kUDDidShowLongTapTo
- (void)setHidden:(BOOL)hidden - (void)setHidden:(BOOL)hidden
{ {
if (!self.hidden && 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]; return [self.sideView setHidden:hidden animated:YES];
} }

View File

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

View File

@@ -23,7 +23,6 @@ enum GlobalStyleSheet: String, CaseIterable {
case trackRecordingWidgetButton = "TrackRecordingWidgetButton" case trackRecordingWidgetButton = "TrackRecordingWidgetButton"
case blackOpaqueBackground = "BlackOpaqueBackground" case blackOpaqueBackground = "BlackOpaqueBackground"
case blueBackground = "BlueBackground" case blueBackground = "BlueBackground"
case toastBackground = "ToastBackground"
case fadeBackground = "FadeBackground" case fadeBackground = "FadeBackground"
case errorBackground = "ErrorBackground" case errorBackground = "ErrorBackground"
case blackStatusBarBackground = "BlackStatusBarBackground" case blackStatusBarBackground = "BlackStatusBarBackground"
@@ -62,6 +61,8 @@ enum GlobalStyleSheet: String, CaseIterable {
case grabber case grabber
case modalSheetBackground case modalSheetBackground
case modalSheetContent case modalSheetContent
case toastBackground
case toastLabel
} }
extension GlobalStyleSheet: IStyleSheet { extension GlobalStyleSheet: IStyleSheet {
@@ -197,10 +198,6 @@ extension GlobalStyleSheet: IStyleSheet {
return .add { s in return .add { s in
s.backgroundColor = colors.linkBlue s.backgroundColor = colors.linkBlue
} }
case .toastBackground:
return .add { s in
s.backgroundColor = colors.toastBackground
}
case .fadeBackground: case .fadeBackground:
return .add { s in return .add { s in
s.backgroundColor = colors.fadeBackground s.backgroundColor = colors.fadeBackground
@@ -452,6 +449,17 @@ extension GlobalStyleSheet: IStyleSheet {
s.backgroundColor = colors.clear s.backgroundColor = colors.clear
s.clip = true s.clip = true
} }
case .toastBackground:
return .add { s in
s.cornerRadius = .modalSheet
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) { private func stop(completion: (CompletionHandler)? = nil) {
guard !trackRecorder.isTrackRecordingEmpty() else { 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) stopRecording(.withoutSaving, completion: completion)
return return
} }

View File

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

View File

@@ -346,7 +346,7 @@ private extension AboutController {
UIPasteboard.general.string = content UIPasteboard.general.string = content
let message = String(format: L("copied_to_clipboard"), content) let message = String(format: L("copied_to_clipboard"), content)
UIImpactFeedbackGenerator(style: .medium).impactOccurred() 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 { func withPriority(_ priority: UILayoutPriority) -> NSLayoutConstraint {
self.priority = priority self.priority = priority
return self return self

View File

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

View File

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