aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2022-02-03 02:33:50 -0600
committerIván Ávalos <avalos@disroot.org>2022-02-03 02:33:50 -0600
commit48e9f6e38213c533286d83e426346471d507467d (patch)
treefa19d37b1ccdcd17ea40181ae3bef6abedb2450e
parent416be1cc70494737efc2fc2409c889db314d7fdd (diff)
downloadetbsa-trackermap-mobile-48e9f6e38213c533286d83e426346471d507467d.tar.gz
etbsa-trackermap-mobile-48e9f6e38213c533286d83e426346471d507467d.tar.bz2
etbsa-trackermap-mobile-48e9f6e38213c533286d83e426346471d507467d.zip
WIP: rewrote MapWrapperView + MapView as unified UIKit view controller
-rw-r--r--iosApp/iosApp.xcodeproj/project.pbxproj50
-rw-r--r--iosApp/iosApp/Map/MapView.swift307
-rw-r--r--iosApp/iosApp/Map/MapViewController.xib104
-rw-r--r--iosApp/iosApp/Map/MapWrapperView.swift87
-rw-r--r--iosApp/iosApp/Map/UnitMapView.swift6
-rw-r--r--iosApp/iosApp/Shared/HyperlinkText.swift186
6 files changed, 242 insertions, 498 deletions
diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj
index f155c39..9f2f41f 100644
--- a/iosApp/iosApp.xcodeproj/project.pbxproj
+++ b/iosApp/iosApp.xcodeproj/project.pbxproj
@@ -19,10 +19,11 @@
E33A236E27A7545500DD647F /* WhirlyGlobeMaplyComponent.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E33A236B27A7543700DD647F /* WhirlyGlobeMaplyComponent.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E33A237127A7553500DD647F /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E33A237027A7553500DD647F /* MapView.swift */; };
E33A237327A7581A00DD647F /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = E33A237227A7581A00DD647F /* Utils.swift */; };
- E34A2F4427A77D9500AD8AEB /* MapWrapperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E34A2F4327A77D9400AD8AEB /* MapWrapperView.swift */; };
E34A2F4827A7878200AD8AEB /* HyperlinkText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E34A2F4727A7878200AD8AEB /* HyperlinkText.swift */; };
E34A2F4B27A7881200AD8AEB /* SwiftUIFlowLayout in Frameworks */ = {isa = PBXBuildFile; productRef = E34A2F4A27A7881200AD8AEB /* SwiftUIFlowLayout */; };
E34A2F4D27A7DB2200AD8AEB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E34A2F4C27A7DB2200AD8AEB /* Localizable.strings */; };
+ E36DF77B27AB740C003C561C /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E36DF77927AB740C003C561C /* MapViewController.swift */; };
+ E36DF77C27AB740C003C561C /* MapViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E36DF77A27AB740C003C561C /* MapViewController.xib */; };
E38F241527A242870069FC45 /* Inject.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38F241427A242870069FC45 /* Inject.swift */; };
E38F241727A242C70069FC45 /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38F241627A242C70069FC45 /* Resolver.swift */; };
E38F241C27A26DD70069FC45 /* RootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38F241B27A26DD70069FC45 /* RootViewModel.swift */; };
@@ -61,9 +62,10 @@
E33A236B27A7543700DD647F /* WhirlyGlobeMaplyComponent.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = WhirlyGlobeMaplyComponent.xcframework; sourceTree = "<group>"; };
E33A237027A7553500DD647F /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = "<group>"; };
E33A237227A7581A00DD647F /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
- E34A2F4327A77D9400AD8AEB /* MapWrapperView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapWrapperView.swift; sourceTree = "<group>"; };
E34A2F4727A7878200AD8AEB /* HyperlinkText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HyperlinkText.swift; sourceTree = "<group>"; };
E34A2F4C27A7DB2200AD8AEB /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
+ E36DF77927AB740C003C561C /* MapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewController.swift; sourceTree = "<group>"; };
+ E36DF77A27AB740C003C561C /* MapViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MapViewController.xib; sourceTree = "<group>"; };
E38F241427A242870069FC45 /* Inject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inject.swift; sourceTree = "<group>"; };
E38F241627A242C70069FC45 /* Resolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = "<group>"; };
E38F241B27A26DD70069FC45 /* RootViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModel.swift; sourceTree = "<group>"; };
@@ -119,6 +121,7 @@
children = (
E34A2F4C27A7DB2200AD8AEB /* Localizable.strings */,
E33A235E27A4FD1C00DD647F /* Map */,
+ E35A078427AB615F00F24D71 /* Details */,
E39ABC4427A4EBB000965D05 /* Devices */,
E38F241A27A2659C0069FC45 /* Units */,
E3E77EE1279D43C000150070 /* Shared */,
@@ -144,11 +147,43 @@
children = (
E33A235F27A4FD2C00DD647F /* UnitMapView.swift */,
E33A237027A7553500DD647F /* MapView.swift */,
- E34A2F4327A77D9400AD8AEB /* MapWrapperView.swift */,
+ E36DF77927AB740C003C561C /* MapViewController.swift */,
+ E36DF77A27AB740C003C561C /* MapViewController.xib */,
);
path = Map;
sourceTree = "<group>";
};
+ E35A078427AB615F00F24D71 /* Details */ = {
+ isa = PBXGroup;
+ children = (
+ E35A078727AB619700F24D71 /* Reports */,
+ E35A078627AB619000F24D71 /* Information */,
+ E35A078527AB618700F24D71 /* Commands */,
+ );
+ path = Details;
+ sourceTree = "<group>";
+ };
+ E35A078527AB618700F24D71 /* Commands */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ path = Commands;
+ sourceTree = "<group>";
+ };
+ E35A078627AB619000F24D71 /* Information */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ path = Information;
+ sourceTree = "<group>";
+ };
+ E35A078727AB619700F24D71 /* Reports */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ path = Reports;
+ sourceTree = "<group>";
+ };
E38F241A27A2659C0069FC45 /* Units */ = {
isa = PBXGroup;
children = (
@@ -229,6 +264,7 @@
TargetAttributes = {
7555FF7A242A565900829871 = {
CreatedOnToolsVersion = 11.3.1;
+ LastSwiftMigration = 1320;
};
};
};
@@ -263,6 +299,7 @@
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */,
E34A2F4D27A7DB2200AD8AEB /* Localizable.strings in Resources */,
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */,
+ E36DF77C27AB740C003C561C /* MapViewController.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -300,12 +337,12 @@
E38F241527A242870069FC45 /* Inject.swift in Sources */,
E39ABC4827A4EDEC00965D05 /* DeviceRow.swift in Sources */,
E38F241C27A26DD70069FC45 /* RootViewModel.swift in Sources */,
+ E36DF77B27AB740C003C561C /* MapViewController.swift in Sources */,
E33A237327A7581A00DD647F /* Utils.swift in Sources */,
E3E77EE6279E6CE400150070 /* FlowCollector.swift in Sources */,
E33A236527A530F300DD647F /* SmallLabelStyle.swift in Sources */,
E39ABC4327A4E88C00965D05 /* UnitsViewModel.swift in Sources */,
E34A2F4827A7878200AD8AEB /* HyperlinkText.swift in Sources */,
- E34A2F4427A77D9500AD8AEB /* MapWrapperView.swift in Sources */,
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
E39ABC4627A4EBD500965D05 /* DevicesView.swift in Sources */,
E38F242027A27B550069FC45 /* LoadingView.swift in Sources */,
@@ -437,6 +474,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
DEVELOPMENT_TEAM = 358YRZ9P3L;
@@ -458,6 +496,8 @@
);
PRODUCT_BUNDLE_IDENTIFIER = mx.trackermap.TrackerMap;
PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
@@ -467,6 +507,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
DEVELOPMENT_TEAM = 358YRZ9P3L;
@@ -488,6 +529,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = mx.trackermap.TrackerMap;
PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
diff --git a/iosApp/iosApp/Map/MapView.swift b/iosApp/iosApp/Map/MapView.swift
index 572db64..6b0fa20 100644
--- a/iosApp/iosApp/Map/MapView.swift
+++ b/iosApp/iosApp/Map/MapView.swift
@@ -21,310 +21,35 @@ import WhirlyGlobeMaplyComponent
import shared
struct MapView: UIViewControllerRepresentable {
- typealias UIViewControllerType = OurMaplyViewController
+ typealias UIViewControllerType = MapViewController
- @Binding var mapLayer: MapLayer
+ @Binding var layer: MapLayer
@Binding var markers: [Marker]
- var markerCallback: ((Int32?) -> Void)?
- var link: BaseMapLink
+ var markerCallback: MarkerCallback?
- class Coordinator: NSObject, MaplyViewControllerDelegate {
- var parent: MapView
- var uiViewController: OurMaplyViewController?
- var loader: MaplyQuadImageLoader? = nil
-
- // Source: https://stackoverflow.com/questions/65923718
- var cancellable: AnyCancellable?
- var link: BaseMapLink? {
- didSet {
- cancellable = link?.$action.sink(receiveValue: { action in
- guard let action = action else {
- return
- }
- self.uiViewController?.action(action)
- })
- }
- }
-
- init(_ uiViewController: MapView) {
- self.parent = uiViewController
- }
-
- func maplyViewController(_ viewC: MaplyViewController,
- didTapAt coord: MaplyCoordinate) {
- if let callback = parent.markerCallback {
- callback(nil)
- }
- }
-
- func maplyViewController(_ viewC: MaplyViewController,
- didSelect selectedObj: NSObject,
- atLoc coord: MaplyCoordinate,
- onScreen screenPt: CGPoint) {
- if let marker = selectedObj as? MaplyScreenMarker {
- if let id = marker.userObject as? Int32 {
- if let callback = parent.markerCallback {
- callback(id)
- }
- }
- }
- }
+ class Coordinator {
+ var shouldCenter: Bool = true
}
func makeCoordinator() -> Coordinator {
- Coordinator(self)
+ return Coordinator()
}
- func makeUIViewController(context: Context) -> OurMaplyViewController {
- let mapViewController = OurMaplyViewController(mapType: .typeFlat)
- mapViewController.delegate = context.coordinator
-
- let tileInfo = Utils.tileInfoFrom(layer: mapLayer)
- mapViewController.setZoomLimits(minZoom: mapLayer.minZoom,
- maxZoom: mapLayer.maxZoom)
-
- let sampleParams = MaplySamplingParams()
- sampleParams.coordSys = MaplySphericalMercator(webStandard: ())
- sampleParams.coverPoles = true
- sampleParams.edgeMatching = true
- sampleParams.minZoom = tileInfo.minZoom
- sampleParams.maxZoom = tileInfo.maxZoom
- sampleParams.singleLevel = true
- sampleParams.maxTiles = 25
-
- let loader = MaplyQuadImageLoader(params: sampleParams, tileInfo: tileInfo, viewC: mapViewController)
- loader?.baseDrawPriority = kMaplyImageLayerDrawPriorityDefault
- loader?.imageFormat = .imageUShort565
- context.coordinator.loader = loader
-
- DispatchQueue.main.async {
- let point = MaplyCoordinateMakeWithDegrees(-100.36, 23.191)
- mapViewController.focusOn(point: point, height: 0.4, animated: false)
- }
-
- return mapViewController
+ func makeUIViewController(context: Context) -> MapViewController {
+ let mapVC = MapViewController()
+ mapVC.markerCallback = markerCallback
+ mapVC.mapLayer = layer
+ return mapVC
}
- func updateUIViewController(_ uiViewController: OurMaplyViewController, context: Context) {
- context.coordinator.uiViewController = uiViewController
- context.coordinator.link = link
-
+ func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
// MARK: - Set map layer
- context.coordinator.loader?.changeTileInfo(Utils.tileInfoFrom(layer: mapLayer))
- uiViewController.setZoomLimits(minZoom: mapLayer.minZoom,
- maxZoom: mapLayer.maxZoom)
+ uiViewController.setMapLayer(layer)
// MARK: - Set markers
uiViewController.display(markers: markers,
- isReport: false)
- }
-
- static func dismantleUIViewController(_ uiViewController: MaplyViewController, coordinator: Coordinator) {
- coordinator.loader?.shutdown()
- uiViewController.teardown()
- }
-}
-
-class OurMaplyViewController: MaplyViewController {
- enum Action {
- case zoomIn
- case zoomOut
- }
-
- private var objects = [MaplyComponentObject]()
- private var geofenceObjects = [MaplyComponentObject]()
-
- func action(_ action: Action) {
- DispatchQueue.main.async {
- switch action {
- case .zoomIn:
- self.zoomIn()
- case .zoomOut:
- self.zoomOut()
- }
- }
- }
-
- func focusOn(point: MaplyCoordinate,
- height: Float = 0.0000264,
- animated: Bool = true) {
- let z = max(height, getMinZoom())
- if animated {
- animate(toPosition: point, height: z, time: 0.2)
- } else {
- setPosition(point, height: z)
- }
- }
-
- func zoomIn() {
- let pos = getPosition()
- let zoom = currentMapScale() / 2
- focusOn(point: pos, height: height(forMapScale: zoom))
- }
-
- func zoomOut() {
- let pos = getPosition()
- let zoom = currentMapScale() * 2
- focusOn(point: pos, height: height(forMapScale: zoom))
- }
-
- func clear(geofences: Bool = false) {
- if geofences {
- remove(geofenceObjects)
- geofenceObjects.removeAll()
- } else {
- remove(objects)
- objects.removeAll()
- }
- }
-
- func display(markers: [Marker],
- isReport: Bool,
- center: Bool = false) {
- clear()
-
- let points = markers.map { marker in
- MaplyCoordinateMakeWithDegrees(Float(marker.longitude),
- Float(marker.latitude))
- }
-
- let fontSize = 11.0
- let colorReport = Color.green
- let colorLabel = Color.secondary
- let colorLabelOutline = Color.systemBackground
-
- let vectorDesc: [AnyHashable : Any] = [
- kMaplyColor: colorReport,
- kMaplyVecWidth: 20.0
- ]
-
- let labelDesc: [AnyHashable : Any] = [
- kMaplyFont: UIFont.boldSystemFont(ofSize: fontSize),
- kMaplyTextColor: colorLabel,
- kMaplyTextOutlineColor: colorLabelOutline,
- kMaplyTextOutlineSize: 3.0
- ]
-
- /* MARK: - Draw markers for positions */
- let screenMarkers = markers.enumerated().map { (i, marker) -> MaplyScreenMarker in
- let screenMarker = MaplyScreenMarker()
- screenMarker.layoutImportance = .greatestFiniteMagnitude
- screenMarker.loc = MaplyCoordinateMakeWithDegrees(Float(marker.longitude),
- Float(marker.latitude))
- var type: Marker.Type_ = .default_
- if isReport {
- // For reports, position, start and end icons must be different
- switch i {
- case markers.startIndex: type = .reportStart
- case markers.endIndex: type = .reportEnd
- default: type = .reportPosition
- }
- } else {
- type = marker.type
- }
- screenMarker.image = getIcon(markerType: type)
-
- var size = 50.0
- if isReport {
- // For reports, position, start and end sizes must be different
- switch i {
- case markers.startIndex: size = 50.0
- case markers.endIndex: size = 50.0
- default: size = 25.0
- }
- }
- screenMarker.size = CGSize(width: size, height: size)
- screenMarker.userObject = marker.id
- screenMarker.selectable = true
-
- return screenMarker
- }
-
- if let objs = addScreenMarkers(screenMarkers, desc: nil, mode: .any) {
- objects.append(objs)
- }
-
- /* MARK: - Add labels for markers */
- if !isReport && !markers.isEmpty {
- let screenLabels = markers.map { marker -> MaplyScreenLabel in
- let label = MaplyScreenLabel()
- label.layoutImportance = .greatestFiniteMagnitude
- var text = marker.name
- if marker.name.count >= 20 {
- let end = marker.name.index(marker.name.startIndex, offsetBy: 20)
- text = String(marker.name[..<end])
- }
- label.text = text
- label.loc = MaplyCoordinateMakeWithDegrees(Float(marker.longitude),
- Float(marker.latitude))
- label.offset = CGPoint(x: 0.0, y: 25.0)
-
- return label
- }
-
- if let objs = addScreenLabels(screenLabels, desc: labelDesc) {
- objects.append(objs)
- }
- }
-
- /* MARK: - Draw polyline for report */
- if isReport && !markers.isEmpty {
- let geoJSON: [AnyHashable : Any] = [
- "type": "FeatureCollection",
- "features": [
- [
- "type": "LineString",
- "coordinates": points.map({ point in
- [point.x, point.y]
- })
- ]
- ]
- ]
- if let vector = MaplyVectorObject(fromGeoJSONDictionary: geoJSON) {
- if let objs = addVectors([vector], desc: vectorDesc, mode: .any) {
- objects.append(objs)
- }
- }
- }
-
- /* MARK: - Center map to bounds */
- if center && !markers.isEmpty {
- let box = MaplyBoundingBoxExpandByFraction(
- MaplyBoundingBoxFromCoordinates(points, UInt32(points.count)), 0.1)
- let center = MaplyCoordinate(x: (box.ur.x + box.ll.x) / 2,
- y: (box.ur.y + box.ll.y) / 2)
- let zoom = max(findHeight(toViewBounds: box, pos: center), getMinZoom())
- setPosition(center, height: zoom)
- }
- }
-
- func setZoomLimits(minZoom: Int32, maxZoom: Int32) {
- setZoomLimitsMin(
- height(forMapScale: Float(truncating:
- MapCalculus.companion.zoomLevelToScale(zoom: maxZoom)
- ?? MapCalculus.companion.zoomLevelToScale(zoom: 21)!
- )),
- max: height(forMapScale: Float(truncating:
- MapCalculus.companion.zoomLevelToScale(zoom: minZoom)
- ?? MapCalculus.companion.zoomLevelToScale(zoom: 1)!
- )))
- }
-
- private func getIcon(markerType: Marker.Type_) -> UIImage {
- return UIImage(named: MarkerTransformations
- .markerTypeToImageName(markerType: markerType))!
- }
-}
-
-// Source: https://stackoverflow.com/questions/65923718
-class BaseMapLink: ObservableObject {
- @Published var action: OurMaplyViewController.Action?
-
- func zoomIn() {
- action = .zoomIn
- }
-
- func zoomOut() {
- action = .zoomOut
+ isReport: false,
+ center: context.coordinator.shouldCenter)
+ context.coordinator.shouldCenter = false
}
}
diff --git a/iosApp/iosApp/Map/MapViewController.xib b/iosApp/iosApp/Map/MapViewController.xib
new file mode 100644
index 0000000..9d1694f
--- /dev/null
+++ b/iosApp/iosApp/Map/MapViewController.xib
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+ <device id="retina5_9" orientation="portrait" appearance="light"/>
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
+ <capability name="Image references" minToolsVersion="12.0"/>
+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+ <capability name="System colors in document resources" minToolsVersion="11.0"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <objects>
+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="MapViewController" customModule="iosApp" customModuleProvider="target">
+ <connections>
+ <outlet property="attributionText" destination="R7q-da-DO1" id="Noy-FR-ZRo"/>
+ <outlet property="mapContainer" destination="T6C-8E-ftt" id="esY-iz-qsl"/>
+ <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
+ </connections>
+ </placeholder>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+ <view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
+ <rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="XUh-lD-GKm">
+ <rect key="frame" x="317" y="52" width="50" height="50"/>
+ <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="50" id="SFA-XB-o3z"/>
+ <constraint firstAttribute="width" constant="50" id="ezA-Bu-6Mq"/>
+ </constraints>
+ <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+ <state key="normal">
+ <color key="titleColor" systemColor="labelColor"/>
+ <imageReference key="image" image="plus" catalog="system" symbolScale="large"/>
+ <preferredSymbolConfiguration key="preferredSymbolConfiguration"/>
+ </state>
+ <connections>
+ <action selector="onZoomInPressed:" destination="-1" eventType="touchUpInside" id="x4P-sk-jdi"/>
+ </connections>
+ </button>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kdu-l3-IK7">
+ <rect key="frame" x="317" y="110" width="50" height="50"/>
+ <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+ <constraints>
+ <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="50" id="2yG-8e-OLn"/>
+ <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="50" id="ACq-Cq-PTq"/>
+ <constraint firstAttribute="width" constant="50" id="Ep3-3d-iue"/>
+ <constraint firstAttribute="height" constant="50" id="gMl-lS-Lvw"/>
+ </constraints>
+ <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+ <state key="normal">
+ <color key="titleColor" systemColor="labelColor"/>
+ <imageReference key="image" image="minus" catalog="system" symbolScale="large"/>
+ <preferredSymbolConfiguration key="preferredSymbolConfiguration"/>
+ </state>
+ <connections>
+ <action selector="onZoomOutPressed:" destination="-1" eventType="touchUpInside" id="rt2-fy-VCy"/>
+ </connections>
+ </button>
+ <textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" alpha="0.40000000000000002" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Copyright (C) 2020 OpenStreetMap" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="R7q-da-DO1">
+ <rect key="frame" x="0.0" y="745" width="375" height="33"/>
+ <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+ <color key="textColor" systemColor="labelColor"/>
+ <fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="14"/>
+ <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
+ </textView>
+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="T6C-8E-ftt">
+ <rect key="frame" x="0.0" y="44" width="375" height="734"/>
+ <color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
+ </view>
+ </subviews>
+ <viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
+ <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+ <constraints>
+ <constraint firstItem="T6C-8E-ftt" firstAttribute="trailing" secondItem="fnl-2z-Ty3" secondAttribute="trailing" id="Adi-2F-rXH"/>
+ <constraint firstItem="T6C-8E-ftt" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" id="EGw-gK-1tb"/>
+ <constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="XUh-lD-GKm" secondAttribute="trailing" constant="8" id="END-FB-FNk"/>
+ <constraint firstItem="T6C-8E-ftt" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" id="HzR-cl-3zE"/>
+ <constraint firstItem="R7q-da-DO1" firstAttribute="bottom" secondItem="fnl-2z-Ty3" secondAttribute="bottom" id="NMu-yb-3sE"/>
+ <constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="kdu-l3-IK7" secondAttribute="trailing" constant="8" id="Um2-vS-uoQ"/>
+ <constraint firstItem="R7q-da-DO1" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" id="XM1-Gu-sfE"/>
+ <constraint firstItem="kdu-l3-IK7" firstAttribute="top" secondItem="XUh-lD-GKm" secondAttribute="bottom" constant="8" id="hTB-Wm-3eQ"/>
+ <constraint firstItem="R7q-da-DO1" firstAttribute="trailing" secondItem="fnl-2z-Ty3" secondAttribute="trailing" id="heK-zH-Ob4"/>
+ <constraint firstItem="T6C-8E-ftt" firstAttribute="bottom" secondItem="fnl-2z-Ty3" secondAttribute="bottom" id="oT6-iC-7PW"/>
+ <constraint firstItem="XUh-lD-GKm" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" constant="8" id="oXT-Ln-tOw"/>
+ </constraints>
+ <point key="canvasLocation" x="140" y="96.059113300492612"/>
+ </view>
+ </objects>
+ <resources>
+ <image name="minus" catalog="system" width="128" height="24"/>
+ <image name="plus" catalog="system" width="128" height="113"/>
+ <systemColor name="labelColor">
+ <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+ </systemColor>
+ <systemColor name="secondarySystemBackgroundColor">
+ <color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ </systemColor>
+ <systemColor name="systemBackgroundColor">
+ <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+ </systemColor>
+ </resources>
+</document>
diff --git a/iosApp/iosApp/Map/MapWrapperView.swift b/iosApp/iosApp/Map/MapWrapperView.swift
deleted file mode 100644
index 7450eed..0000000
--- a/iosApp/iosApp/Map/MapWrapperView.swift
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * TrackerMap
- * Copyright (C) 2021-2022 Iván Ávalos <avalos@disroot.org>, Henoch Ojeda <imhenoch@protonmail.com>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-import SwiftUI
-import Combine
-import WhirlyGlobeMaplyComponent
-import shared
-
-struct MapWrapperView: View {
- @Binding var layer: MapLayer
- @Binding var markers: [Marker]
- var markerCallback: ((Int32?) -> Void)?
- @ObservedObject var link: BaseMapLink = BaseMapLink()
-
- var body: some View {
- ZStack {
- // MARK: - Map
- MapView(mapLayer: $layer,
- markers: $markers,
- markerCallback: markerCallback,
- link: link)
-
- // MARK: - Attribution
- VStack {
- HyperlinkText(html: layer.attribution)
- .font(.footnote)
- .lineLimit(3)
- .foregroundColor(.label.opacity(0.35))
- .padding(6.0)
- .background(.systemBackground.opacity(0.35))
- }
- .frame(maxWidth: .infinity,
- maxHeight: .infinity,
- alignment: .bottom)
- .allowsHitTesting(false)
-
- // MARK: - Controls
- VStack {
- // MARK: Zoom in
- Button {
- print ("Zoom in!")
- link.zoomIn()
- } label: {
- Image(systemName: "plus").imageScale(.large)
- }
- .buttonStyle(ControlButtonStyle())
-
- // MARK: Zoom out
- Button {
- print("Zoom out!")
- link.zoomOut()
- } label: {
- Image(systemName: "minus").imageScale(.large)
- }
- .buttonStyle(ControlButtonStyle())
- }
- .frame(maxWidth: .infinity,
- maxHeight: .infinity,
- alignment: .topTrailing)
- .padding()
- }
- }
-}
-
-struct ControlButtonStyle: ButtonStyle {
- func makeBody(configuration: Configuration) -> some View {
- configuration.label
- .frame(width: 50, height: 50)
- .foregroundColor(configuration.isPressed ? .secondary : .primary)
- .background(configuration.isPressed ? .secondarySystemBackground : .systemBackground)
- .clipShape(Circle())
- }
-}
diff --git a/iosApp/iosApp/Map/UnitMapView.swift b/iosApp/iosApp/Map/UnitMapView.swift
index e1f90a0..5f418f1 100644
--- a/iosApp/iosApp/Map/UnitMapView.swift
+++ b/iosApp/iosApp/Map/UnitMapView.swift
@@ -23,9 +23,9 @@ struct UnitMapView: View {
var body: some View {
ZStack {
- MapWrapperView(layer: $unitsViewModel.mapLayerType,
- markers: $unitsViewModel.markers,
- markerCallback: unitsViewModel.selectUnitWith)
+ MapView(layer: $unitsViewModel.mapLayerType,
+ markers: $unitsViewModel.markers,
+ markerCallback: unitsViewModel.selectUnitWith)
if let unit = unitsViewModel.selectedUnit {
VStack {
DeviceRow(unit: unit, callback: { action in
diff --git a/iosApp/iosApp/Shared/HyperlinkText.swift b/iosApp/iosApp/Shared/HyperlinkText.swift
index 69c758b..a735f8d 100644
--- a/iosApp/iosApp/Shared/HyperlinkText.swift
+++ b/iosApp/iosApp/Shared/HyperlinkText.swift
@@ -15,129 +15,89 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-import SwiftUI
-import SwiftUIFlowLayout
+import Foundation
+import UIKit
-// Source: https://swiftuirecipes.com/blog/hyperlinks-in-swiftui-text
-struct HyperlinkText: View {
- private let pairs: [StringWithAttributes]
-
- init(_ attributedString: NSAttributedString) {
- pairs = attributedString.stringsWithAttributes
+// Source: https://stackoverflow.com/a/50272137
+extension UIColor {
+ enum HexFormat {
+ case RGB
+ case ARGB
+ case RGBA
+ case RRGGBB
+ case AARRGGBB
+ case RRGGBBAA
}
-
- init?(html: String) {
- if let data = html.data(using: .utf8),
- let attributedString = try? NSAttributedString(data: data,
- options: [.documentType: NSAttributedString.DocumentType.html],
- documentAttributes: nil) {
- self.init(attributedString)
- } else {
- return nil
- }
- }
-
- var body: some View {
- FlowLayout(mode: .vstack,
- binding: .constant(false),
- items: pairs,
- itemSpacing: 0) { pair in
- if let link = pair.attrs[.link],
- let url = link as? URL {
- Text(pair)
- .onTapGesture {
- if UIApplication.shared.canOpenURL(url) {
- UIApplication.shared.open(url)
- }
- }
- } else {
- Text(pair)
- }
- }
- }
-}
-struct StringWithAttributes: Hashable, Identifiable {
- let id = UUID()
- let string: String
- let attrs: [NSAttributedString.Key: Any]
-
- static func == (lhs: StringWithAttributes, rhs: StringWithAttributes) -> Bool {
- lhs.id == rhs.id
- }
-
- func hash(into hasher: inout Hasher) {
- hasher.combine(id)
+ enum HexDigits {
+ case d3, d4, d6, d8
}
-}
-extension NSAttributedString {
- var stringsWithAttributes: [StringWithAttributes] {
- var attributes = [StringWithAttributes]()
- enumerateAttributes(in: NSRange(location: 0, length: length), options: []) { (attrs, range, _) in
- let string = attributedSubstring(from: range).string
- attributes.append(StringWithAttributes(string: string, attrs: attrs))
- }
- return attributes
- }
-}
+ func hexString(_ format: HexFormat = .RRGGBBAA) -> String {
+ let maxi = [.RGB, .ARGB, .RGBA].contains(format) ? 16 : 256
-extension Text {
- init(_ singleAttribute: StringWithAttributes) {
- let string = singleAttribute.string
- let attrs = singleAttribute.attrs
- var text = Text(string)
-
- /*if let font = attrs[.font] as? UIFont {
- text = text.font(.init(font))
- }
-
- if let color = attrs[.foregroundColor] as? UIColor {
- text = text.foregroundColor(Color(color))
- }*/
-
- if let kern = attrs[.kern] as? CGFloat {
- text = text.kerning(kern)
- }
-
- if #available(iOS 14.0, *) {
- if let tracking = attrs[.tracking] as? CGFloat {
- text = text.tracking(tracking)
- }
+ func toI(_ f: CGFloat) -> Int {
+ return min(maxi - 1, Int(CGFloat(maxi) * f))
}
-
- if let strikethroughStyle = attrs[.strikethroughStyle] as? NSNumber, strikethroughStyle != 0 {
- if let strikethroughColor = (attrs[.strikethroughColor] as? UIColor) {
- text = text.strikethrough(true, color: Color(strikethroughColor))
- } else {
- text = text.strikethrough(true)
- }
- }
-
- if let underlineStyle = attrs[.underlineStyle] as? NSNumber,
- underlineStyle != 0 {
- if let underlineColor = (attrs[.underlineColor] as? UIColor) {
- text = text.underline(true, color: Color(underlineColor))
- } else {
- text = text.underline(true)
- }
- }
-
- if let baselineOffset = attrs[.baselineOffset] as? NSNumber {
- text = text.baselineOffset(CGFloat(baselineOffset.floatValue))
+
+ var r: CGFloat = 0
+ var g: CGFloat = 0
+ var b: CGFloat = 0
+ var a: CGFloat = 0
+
+ self.getRed(&r, green: &g, blue: &b, alpha: &a)
+
+ let ri = toI(r)
+ let gi = toI(g)
+ let bi = toI(b)
+ let ai = toI(a)
+
+ switch format {
+ case .RGB: return String(format: "#%X%X%X", ri, gi, bi)
+ case .ARGB: return String(format: "#%X%X%X%X", ai, ri, gi, bi)
+ case .RGBA: return String(format: "#%X%X%X%X", ri, gi, bi, ai)
+ case .RRGGBB: return String(format: "#%02X%02X%02X", ri, gi, bi)
+ case .AARRGGBB: return String(format: "#%02X%02X%02X%02X", ai, ri, gi, bi)
+ case .RRGGBBAA: return String(format: "#%02X%02X%02X%02X", ri, gi, bi, ai)
}
-
- self = text
}
-
- init(_ attributes: [StringWithAttributes]) {
- self.init("")
- for singleAttribute in attributes {
- self = self + Text(singleAttribute)
+
+ func hexString(_ digits: HexDigits) -> String {
+ switch digits {
+ case .d3: return hexString(.RGB)
+ case .d4: return hexString(.RGBA)
+ case .d6: return hexString(.RRGGBB)
+ case .d8: return hexString(.RRGGBBAA)
}
}
-
- init(_ attributedString: NSAttributedString) {
- self.init(attributedString.stringsWithAttributes)
+}
+
+// Source: https://swiftuirecipes.com/blog/swiftui-text-with-html-via-nsattributedstring
+class HtmlString {
+ static func htmlToAttrStr(_ html: String, size: CGFloat, color: UIColor) -> NSAttributedString? {
+ let fullHTML = """
+ <!doctype html>
+ <html>
+ <head>
+ <style>
+ body {
+ font-family: -apple-system;
+ font-size: \(size)px;
+ color: \(color.hexString(.RGB))
+ }
+ </style>
+ </head>
+ <body>
+ \(html)
+ </body>
+ </html>
+ """
+ if let data = fullHTML.data(using: .utf8) {
+ return try? NSAttributedString(
+ data: data,
+ options: [.documentType: NSAttributedString.DocumentType.html],
+ documentAttributes: nil)
+ }
+ return nil
}
}