Merge 5c3b8ccdfe
into 90772d66d2
This commit is contained in:
commit
4de9a6a640
10 changed files with 99 additions and 80 deletions
|
@ -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 }) }
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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]];
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ final class DonationView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
private extension NSLayoutConstraint {
|
||||
extension NSLayoutConstraint {
|
||||
func withPriority(_ priority: UILayoutPriority) -> NSLayoutConstraint {
|
||||
self.priority = priority
|
||||
return self
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Reference in a new issue