Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,144 @@
class AvailableArea: UIView {
private enum Const {
static let observeKeyPath = "sublayers"
}
var deferNotification: Bool { return true }
private(set) var orientation = UIDeviceOrientation.unknown {
didSet {
scheduleNotification()
}
}
var shouldUpdateAreaFrame: Bool {
if let insets = UIApplication.shared.delegate?.window??.safeAreaInsets {
return insets.left > 0 || insets.right > 0
} else {
return false
}
}
var areaFrame: CGRect {
return alternative(iPhone: {
var frame = self.frame
if self.shouldUpdateAreaFrame {
switch self.orientation {
case .landscapeLeft:
frame.origin.x -= 16
frame.size.width += 60
case .landscapeRight:
frame.origin.x -= 44
frame.size.width += 60
default: break
}
}
return frame
}, iPad: { self.frame })()
}
private var affectingViews = Set<UIView>()
override func didMoveToSuperview() {
super.didMoveToSuperview()
subscribe()
update()
}
deinit {
unsubscribe()
}
private func subscribe() {
guard let ol = superview?.layer else { return }
ol.addObserver(self, forKeyPath: Const.observeKeyPath, options: .new, context: nil)
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
let nc = NotificationCenter.default
nc.addObserver(forName: UIDevice.orientationDidChangeNotification, object: nil, queue: .main) { _ in
let orientation = UIDevice.current.orientation
guard !orientation.isFlat && orientation != .portraitUpsideDown else { return }
self.orientation = orientation
}
}
private func unsubscribe() {
guard let ol = superview?.layer else { return }
ol.removeObserver(self, forKeyPath: Const.observeKeyPath)
UIDevice.current.endGeneratingDeviceOrientationNotifications()
}
override func observeValue(forKeyPath keyPath: String?, of _: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) {
if keyPath == Const.observeKeyPath {
DispatchQueue.main.async {
self.update()
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
scheduleNotification()
}
private func newAffectingViews(view: UIView) -> Set<UIView> {
var views = Set<UIView>()
if isAreaAffectingView(view) {
views.insert(view)
}
view.subviews.forEach {
views.formUnion(newAffectingViews(view: $0))
}
return views
}
private func update() {
guard let sv = superview else { return }
let newAVs = newAffectingViews(view: sv)
newAVs.subtracting(affectingViews).forEach(addAffectingView)
affectingViews = newAVs
scheduleNotification()
}
func addConstraints(otherView: UIView, directions: MWMAvailableAreaAffectDirections) {
guard !directions.isEmpty else {
LOG(.warning, "Attempt to add empty affecting directions from \(otherView) to \(self)")
return
}
let add = { (sa: NSLayoutConstraint.Attribute, oa: NSLayoutConstraint.Attribute, rel: NSLayoutConstraint.Relation) in
let c = NSLayoutConstraint(item: self, attribute: sa, relatedBy: rel, toItem: otherView, attribute: oa, multiplier: 1, constant: 0)
c.priority = UILayoutPriority.defaultHigh
c.isActive = true
}
[
.top: (.top, .bottom, .greaterThanOrEqual),
.bottom: (.bottom, .top, .lessThanOrEqual),
.left: (.left, .right, .greaterThanOrEqual),
.right: (.right, .left, .lessThanOrEqual),
]
.filter { directions.contains($0.key) }
.map { $0.value }
.forEach(add)
}
@objc
private func scheduleNotification() {
if deferNotification {
let selector = #selector(notifyObserver)
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: selector, object: nil)
perform(selector, with: nil, afterDelay: 0)
} else {
notifyObserver()
}
}
func isAreaAffectingView(_: UIView) -> Bool { return false }
func addAffectingView(_: UIView) {}
@objc func notifyObserver() {}
}
extension MWMAvailableAreaAffectDirections: Hashable {
public var hashValue: Int {
return rawValue
}
}

View file

@ -0,0 +1,7 @@
typedef NS_OPTIONS(NSInteger, MWMAvailableAreaAffectDirections) {
MWMAvailableAreaAffectDirectionsNone = 0,
MWMAvailableAreaAffectDirectionsTop = 1 << 0,
MWMAvailableAreaAffectDirectionsBottom = 1 << 1,
MWMAvailableAreaAffectDirectionsLeft = 1 << 2,
MWMAvailableAreaAffectDirectionsRight = 1 << 3
};

View file

@ -0,0 +1,21 @@
final class NavigationInfoArea: AvailableArea {
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.navigationInfoAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.navigationInfoAreaAffectView
let directions = ov.navigationInfoAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
MWMNavigationDashboardManager.updateNavigationInfoAvailableArea(areaFrame)
}
}
extension UIView {
var navigationInfoAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var navigationInfoAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,25 @@
final class PlacePageArea: AvailableArea {
override var areaFrame: CGRect {
return frame
}
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.placePageAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.placePageAreaAffectView
let directions = ov.placePageAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
MWMPlacePageManagerHelper.updateAvailableArea(areaFrame)
}
}
extension UIView {
@objc var placePageAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var placePageAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,23 @@
final class SideButtonsArea: AvailableArea {
override var deferNotification: Bool { return false }
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.sideButtonsAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.sideButtonsAreaAffectView
let directions = ov.sideButtonsAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
MWMSideButtons.updateAvailableArea(areaFrame)
}
}
extension UIView {
@objc var sideButtonsAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var sideButtonsAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,29 @@
final class TabBarArea: AvailableArea {
override var areaFrame: CGRect {
var areaFrame = frame
// Spacing is used only for devices with zero bottom safe area (such as SE).
let additionalBottomSpacing: CGFloat = MapsAppDelegate.theApp().window.safeAreaInsets.bottom.isZero ? -10 : .zero
areaFrame.origin.y += additionalBottomSpacing
return areaFrame
}
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.tabBarAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.tabBarAreaAffectView
let directions = ov.tabBarAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
BottomTabBarViewController.updateAvailableArea(areaFrame)
}
}
extension UIView {
@objc var tabBarAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var tabBarAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,21 @@
final class TrackRecordingButtonArea: AvailableArea {
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.trackRecordingButtonAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.trackRecordingButtonAreaAffectView
let directions = ov.trackRecordingButtonAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
TrackRecordingButtonViewController.updateAvailableArea(areaFrame)
}
}
extension UIView {
@objc var trackRecordingButtonAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var trackRecordingButtonAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,21 @@
final class TrafficButtonArea: AvailableArea {
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.trafficButtonAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.trafficButtonAreaAffectView
let directions = ov.trafficButtonAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
MWMTrafficButtonViewController.updateAvailableArea(areaFrame)
}
}
extension UIView {
@objc var trafficButtonAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var trafficButtonAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,24 @@
final class VisibleArea: AvailableArea {
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.visibleAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.visibleAreaAffectView
let directions = ov.visibleAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
if CarPlayService.shared.isCarplayActivated {
return
}
FrameworkHelper.setVisibleViewport(areaFrame, scaleFactor: MapViewController.shared()?.mapView.contentScaleFactor ?? 1.0)
}
}
extension UIView {
@objc var visibleAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var visibleAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,30 @@
final class WidgetsArea: AvailableArea {
override var areaFrame: CGRect {
return alternative(iPhone: {
var frame = super.areaFrame
frame.origin.y -= 16
frame.size.height += 16
return frame
}, iPad: { super.areaFrame })()
}
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.widgetsAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.widgetsAreaAffectView
let directions = ov.widgetsAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
MWMMapWidgetsHelper.updateAvailableArea(areaFrame)
}
}
extension UIView {
@objc var widgetsAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var widgetsAreaAffectView: UIView { return self }
}