aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2022-03-02 00:18:30 -0600
committerIván Ávalos <avalos@disroot.org>2022-03-02 00:18:30 -0600
commit440399490ec8874ed5295ca6b5770c94d47d4f3d (patch)
tree1538e563438b551a62d9fad8fe39e6bd36af7948
parent7522eaa1cf95820717f6d91a8b5879f5e14cfae2 (diff)
parent9b8eee4362c368e682bfe18d5cef44d6b9d109bf (diff)
downloadetbsa-trackermap-mobile-440399490ec8874ed5295ca6b5770c94d47d4f3d.tar.gz
etbsa-trackermap-mobile-440399490ec8874ed5295ca6b5770c94d47d4f3d.tar.bz2
etbsa-trackermap-mobile-440399490ec8874ed5295ca6b5770c94d47d4f3d.zip
Merged 'main' from upstream
-rw-r--r--iosApp/iosApp.xcodeproj/project.pbxproj47
-rw-r--r--iosApp/iosApp/Assets.xcassets/MapReportEnd.imageset/Contents.json21
-rw-r--r--iosApp/iosApp/Assets.xcassets/MapReportEnd.imageset/map_report_end.svg11
-rw-r--r--iosApp/iosApp/Assets.xcassets/MapReportPosition.imageset/Contents.json21
-rw-r--r--iosApp/iosApp/Assets.xcassets/MapReportPosition.imageset/map_report_position.svg11
-rw-r--r--iosApp/iosApp/Assets.xcassets/MapReportStart.imageset/Contents.json21
-rw-r--r--iosApp/iosApp/Assets.xcassets/MapReportStart.imageset/map_report_start.svg11
-rw-r--r--iosApp/iosApp/Details/DetailsView.swift2
-rw-r--r--iosApp/iosApp/Details/Reports/UnitReportsView.swift148
-rw-r--r--iosApp/iosApp/Details/Reports/UnitReportsViewModel.swift94
-rw-r--r--iosApp/iosApp/Devices/DeviceRow.swift10
-rw-r--r--iosApp/iosApp/Map/MapView.swift14
-rw-r--r--iosApp/iosApp/Map/MapViewController.swift128
-rw-r--r--iosApp/iosApp/Map/UnitMapView.swift1
-rw-r--r--iosApp/iosApp/Shared/MarkerTransformations.swift4
-rw-r--r--iosApp/iosApp/Shared/SidewaysScroller.swift42
-rw-r--r--iosApp/iosApp/Units/UnitsView.swift2
-rw-r--r--iosApp/iosApp/Units/UnitsViewModel.swift7
-rw-r--r--iosApp/iosApp/en.lproj/Localizable.strings39
-rw-r--r--iosApp/iosApp/es-419.lproj/Localizable.strings39
-rw-r--r--shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/ReportController.kt6
21 files changed, 648 insertions, 31 deletions
diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj
index fd6c044..146f7c5 100644
--- a/iosApp/iosApp.xcodeproj/project.pbxproj
+++ b/iosApp/iosApp.xcodeproj/project.pbxproj
@@ -12,6 +12,8 @@
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
7555FF83242A565900829871 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* RootView.swift */; };
E312E74F27B366060018C5DE /* DevicesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E312E74E27B366060018C5DE /* DevicesViewModel.swift */; };
+ E3141B8B27B9E91F00CE777C /* UnitReportsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3141B8A27B9E91F00CE777C /* UnitReportsViewModel.swift */; };
+ E31D721527CF159900CDA320 /* SidewaysScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = E31D721427CF159900CDA320 /* SidewaysScroller.swift */; };
E3385A4F27B0D8A10025311C /* UnitCommandsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3385A4E27B0D8A10025311C /* UnitCommandsViewModel.swift */; };
E33A236027A4FD2C00DD647F /* UnitMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E33A235F27A4FD2C00DD647F /* UnitMapView.swift */; };
E33A236527A530F300DD647F /* SmallLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E33A236427A530F300DD647F /* SmallLabelStyle.swift */; };
@@ -44,6 +46,8 @@
E39ABC4327A4E88C00965D05 /* UnitsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E39ABC4227A4E88C00965D05 /* UnitsViewModel.swift */; };
E39ABC4627A4EBD500965D05 /* DevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E39ABC4527A4EBD500965D05 /* DevicesView.swift */; };
E39ABC4827A4EDEC00965D05 /* DeviceRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E39ABC4727A4EDEC00965D05 /* DeviceRow.swift */; };
+ E3C651E727CB5426002F6F4C /* Tabler in Frameworks */ = {isa = PBXBuildFile; productRef = E3C651E627CB5426002F6F4C /* Tabler */; };
+ E3C651EA27CB61EE002F6F4C /* GEOSwift in Frameworks */ = {isa = PBXBuildFile; productRef = E3C651E927CB61EE002F6F4C /* GEOSwift */; };
E3E77EE6279E6CE400150070 /* FlowCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3E77EE5279E6CE400150070 /* FlowCollector.swift */; };
/* End PBXBuildFile section */
@@ -69,6 +73,8 @@
7555FF82242A565900829871 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
E312E74E27B366060018C5DE /* DevicesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevicesViewModel.swift; sourceTree = "<group>"; };
+ E3141B8A27B9E91F00CE777C /* UnitReportsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitReportsViewModel.swift; sourceTree = "<group>"; };
+ E31D721427CF159900CDA320 /* SidewaysScroller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidewaysScroller.swift; sourceTree = "<group>"; };
E3385A4E27B0D8A10025311C /* UnitCommandsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitCommandsViewModel.swift; sourceTree = "<group>"; };
E33A235F27A4FD2C00DD647F /* UnitMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitMapView.swift; sourceTree = "<group>"; };
E33A236427A530F300DD647F /* SmallLabelStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallLabelStyle.swift; sourceTree = "<group>"; };
@@ -108,8 +114,10 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ E3C651E727CB5426002F6F4C /* Tabler in Frameworks */,
E36A5A8627B4BFC40070DED5 /* FirebaseMessaging in Frameworks */,
E33A236A27A6898700DD647F /* SwiftUIX in Frameworks */,
+ E3C651EA27CB61EE002F6F4C /* GEOSwift in Frameworks */,
E396281D27AF5723005D070E /* WhirlyGlobe.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -214,6 +222,7 @@
isa = PBXGroup;
children = (
E396282527AFBD4E005D070E /* UnitReportsView.swift */,
+ E3141B8A27B9E91F00CE777C /* UnitReportsViewModel.swift */,
);
path = Reports;
sourceTree = "<group>";
@@ -261,6 +270,7 @@
E33A236627A64E4500DD647F /* MarkerTransformations.swift */,
E33A237227A7581A00DD647F /* Utils.swift */,
E34A2F4727A7878200AD8AEB /* HyperlinkText.swift */,
+ E31D721427CF159900CDA320 /* SidewaysScroller.swift */,
);
path = Shared;
sourceTree = "<group>";
@@ -286,6 +296,8 @@
packageProductDependencies = (
E33A236927A6898700DD647F /* SwiftUIX */,
E36A5A8527B4BFC40070DED5 /* FirebaseMessaging */,
+ E3C651E627CB5426002F6F4C /* Tabler */,
+ E3C651E927CB61EE002F6F4C /* GEOSwift */,
);
productName = iosApp;
productReference = 7555FF7B242A565900829871 /* iosApp.app */;
@@ -320,6 +332,8 @@
packageReferences = (
E33A236827A6898700DD647F /* XCRemoteSwiftPackageReference "SwiftUIX" */,
E36A5A8427B4BFC40070DED5 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
+ E3C651E527CB5425002F6F4C /* XCRemoteSwiftPackageReference "SwiftTabler" */,
+ E3C651E827CB61EE002F6F4C /* XCRemoteSwiftPackageReference "GEOSwift" */,
);
productRefGroup = 7555FF7C242A565900829871 /* Products */;
projectDirPath = "";
@@ -396,8 +410,10 @@
E34A2F4827A7878200AD8AEB /* HyperlinkText.swift in Sources */,
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
E39ABC4627A4EBD500965D05 /* DevicesView.swift in Sources */,
+ E3141B8B27B9E91F00CE777C /* UnitReportsViewModel.swift in Sources */,
E38F242027A27B550069FC45 /* LoadingView.swift in Sources */,
E38F241E27A270D50069FC45 /* UnitsView.swift in Sources */,
+ E31D721527CF159900CDA320 /* SidewaysScroller.swift in Sources */,
7555FF83242A565900829871 /* RootView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -541,7 +557,6 @@
CODE_SIGN_ENTITLEMENTS = iosApp/iosApp.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
DEVELOPMENT_TEAM = 358YRZ9P3L;
ENABLE_PREVIEWS = YES;
@@ -555,7 +570,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0.1;
+ MARKETING_VERSION = 1.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
@@ -593,7 +608,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0.1;
+ MARKETING_VERSION = 1.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
@@ -648,6 +663,22 @@
minimumVersion = 8.0.0;
};
};
+ E3C651E527CB5425002F6F4C /* XCRemoteSwiftPackageReference "SwiftTabler" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/openalloc/SwiftTabler";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 0.6.0;
+ };
+ };
+ E3C651E827CB61EE002F6F4C /* XCRemoteSwiftPackageReference "GEOSwift" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/GEOSwift/GEOSwift";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 9.0.0;
+ };
+ };
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@@ -661,6 +692,16 @@
package = E36A5A8427B4BFC40070DED5 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseMessaging;
};
+ E3C651E627CB5426002F6F4C /* Tabler */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = E3C651E527CB5425002F6F4C /* XCRemoteSwiftPackageReference "SwiftTabler" */;
+ productName = Tabler;
+ };
+ E3C651E927CB61EE002F6F4C /* GEOSwift */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = E3C651E827CB61EE002F6F4C /* XCRemoteSwiftPackageReference "GEOSwift" */;
+ productName = GEOSwift;
+ };
/* End XCSwiftPackageProductDependency section */
};
rootObject = 7555FF73242A565900829871 /* Project object */;
diff --git a/iosApp/iosApp/Assets.xcassets/MapReportEnd.imageset/Contents.json b/iosApp/iosApp/Assets.xcassets/MapReportEnd.imageset/Contents.json
new file mode 100644
index 0000000..40c489c
--- /dev/null
+++ b/iosApp/iosApp/Assets.xcassets/MapReportEnd.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "map_report_end.svg",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/iosApp/iosApp/Assets.xcassets/MapReportEnd.imageset/map_report_end.svg b/iosApp/iosApp/Assets.xcassets/MapReportEnd.imageset/map_report_end.svg
new file mode 100644
index 0000000..ec134ba
--- /dev/null
+++ b/iosApp/iosApp/Assets.xcassets/MapReportEnd.imageset/map_report_end.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ width="24"
+ height="24"
+ viewBox="0 0 24 24">
+ <path
+ fill="#008000"
+ d="M12.36,6H7v6h7.24l0.4,2H18V8h-5.24z"/>
+ <path
+ fill="@android:color/black"
+ d="M14.4,6L14,4L5,4v17h2v-7h5.6l0.4,2h7L20,6h-5.6zM18,14h-3.36l-0.4,-2L7,12L7,6h5.36l0.4,2L18,8v6z"/>
+</svg>
diff --git a/iosApp/iosApp/Assets.xcassets/MapReportPosition.imageset/Contents.json b/iosApp/iosApp/Assets.xcassets/MapReportPosition.imageset/Contents.json
new file mode 100644
index 0000000..3a7979a
--- /dev/null
+++ b/iosApp/iosApp/Assets.xcassets/MapReportPosition.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "map_report_position.svg",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/iosApp/iosApp/Assets.xcassets/MapReportPosition.imageset/map_report_position.svg b/iosApp/iosApp/Assets.xcassets/MapReportPosition.imageset/map_report_position.svg
new file mode 100644
index 0000000..c6d39ac
--- /dev/null
+++ b/iosApp/iosApp/Assets.xcassets/MapReportPosition.imageset/map_report_position.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ width="24"
+ height="24"
+ viewBox="0 0 24 24">
+ <path
+ fill="#008000"
+ d="M12,18c3.31,0 6,-2.69 6,-6s-2.69,-6 -6,-6 -6,2.69 -6,6 2.69,6 6,6z"/>
+ <path
+ fill="@android:color/black"
+ d="M12,20c4.42,0 8,-3.58 8,-8s-3.58,-8 -8,-8 -8,3.58 -8,8 3.58,8 8,8zM12,6c3.31,0 6,2.69 6,6s-2.69,6 -6,6 -6,-2.69 -6,-6 2.69,-6 6,-6z"/>
+</svg>
diff --git a/iosApp/iosApp/Assets.xcassets/MapReportStart.imageset/Contents.json b/iosApp/iosApp/Assets.xcassets/MapReportStart.imageset/Contents.json
new file mode 100644
index 0000000..3a1438a
--- /dev/null
+++ b/iosApp/iosApp/Assets.xcassets/MapReportStart.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "map_report_start.svg",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/iosApp/iosApp/Assets.xcassets/MapReportStart.imageset/map_report_start.svg b/iosApp/iosApp/Assets.xcassets/MapReportStart.imageset/map_report_start.svg
new file mode 100644
index 0000000..59a6142
--- /dev/null
+++ b/iosApp/iosApp/Assets.xcassets/MapReportStart.imageset/map_report_start.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ width="24"
+ height="24"
+ viewBox="0 0 24 24">
+ <path
+ fill="#008000"
+ d="M12,20c4.41,0 8,-3.59 8,-8s-3.59,-8 -8,-8 -8,3.59 -8,8 3.59,8 8,8zM10,7.5l6,4.5 -6,4.5v-9z"/>
+ <path
+ fill="@android:color/black"
+ d="M12,22c5.52,0 10,-4.48 10,-10S17.52,2 12,2 2,6.48 2,12s4.48,10 10,10zM12,4c4.41,0 8,3.59 8,8s-3.59,8 -8,8 -8,-3.59 -8,-8 3.59,-8 8,-8zM10,7.5v9l6,-4.5z"/>
+</svg>
diff --git a/iosApp/iosApp/Details/DetailsView.swift b/iosApp/iosApp/Details/DetailsView.swift
index c869369..9b75aec 100644
--- a/iosApp/iosApp/Details/DetailsView.swift
+++ b/iosApp/iosApp/Details/DetailsView.swift
@@ -50,7 +50,7 @@ struct DetailsView: View {
.navigationBarTitleView(
Picker(selection: $action) {
Text("details").tag(DeviceRow.Action.details)
- //Text("reports").tag(DeviceRow.Action.reports)
+ Text("reports").tag(DeviceRow.Action.reports)
Text("commands").tag(DeviceRow.Action.commands)
} label: {
EmptyView()
diff --git a/iosApp/iosApp/Details/Reports/UnitReportsView.swift b/iosApp/iosApp/Details/Reports/UnitReportsView.swift
index 26ddead..2143b3b 100644
--- a/iosApp/iosApp/Details/Reports/UnitReportsView.swift
+++ b/iosApp/iosApp/Details/Reports/UnitReportsView.swift
@@ -17,11 +17,155 @@
*/
import SwiftUI
import shared
+import SwiftUIX
+import Tabler
+
+extension EventInformation: Identifiable {}
struct UnitReportsView: View {
+ @StateObject var unitReportsViewModel: UnitReportsViewModel
+
var unit: UnitInformation
-
+
+ init (unit: UnitInformation) {
+ self.unit = unit
+ self._unitReportsViewModel = StateObject(
+ wrappedValue: UnitReportsViewModel(deviceId: unit.device.id))
+ }
+
+ private var eventsConfig: TablerListConfig<EventInformation> {
+ TablerListConfig<EventInformation>(gridItems: eventsGridItems)
+ }
+
+ private var eventsGridItems: [GridItem] = [
+ GridItem(.flexible(minimum: 100), alignment: .leading),
+ GridItem(.flexible(minimum: 100), alignment: .leading),
+ GridItem(.flexible(minimum: 100, maximum: 150), alignment: .leading),
+ GridItem(.flexible(minimum: 150, maximum: 250), alignment: .leading),
+ ]
+
+ @ViewBuilder
+ private func eventsHeader(_ ctx: Binding<TablerContext<EventInformation>>) -> some View {
+ Text("events-table-datetime")
+ Text("events-table-event")
+ Text("events-table-geofence")
+ Text("events-table-address")
+ }
+
+ @ViewBuilder
+ private func eventsRow(_ element: EventInformation) -> some View {
+ if let eventTime = element.event.eventTime {
+ Text(Formatter.companion.formatDate(str: eventTime))
+ } else { Text("") }
+
+ if let eventType = element.event.type {
+ switch EventInformation.companion.stringToReportType(s: eventType) {
+ case .deviceOnline: Text("event-device-online")
+ case .deviceUnknown: Text("event-device-unknown")
+ case .deviceOffline: Text("event-device-offline")
+ case .deviceInactive: Text("event-device-inactive")
+ case .deviceMoving: Text("event-device-moving")
+ case .deviceStopped: Text("event-device-stopped")
+ case .deviceOverspeed: Text("event-device-overspeed")
+ case .deviceFuelDrop: Text("event-device-fuel-drop")
+ case .commandResult: Text("event-command-result")
+ case .geofenceEnter: Text("event-geofence-enter")
+ case .geofenceExit: Text("event-geofence-exit")
+ case .alarm: Text("event-alarm")
+ case .ignitionOn: Text("event-ignition-on")
+ case .ignitionOff: Text("event-ignition-off")
+ case .maintenance: Text("event-maintenance")
+ case .textMessage: Text("event-text-message")
+ case .driverChanged: Text("event-driver-changed")
+ default: Text("event-unknown")
+ }
+ } else { Text("") }
+
+ if let geofenceName = element.geofence?.name {
+ Text(geofenceName)
+ } else { Text("") }
+
+ if let positionAddress = element.position?.address {
+ Text(positionAddress)
+ } else { Text("") }
+ }
+
var body: some View {
- Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
+ VStack {
+ if unitReportsViewModel.reportType == .events {
+ if let report = unitReportsViewModel.report as? ReportController.ReportEventsReport {
+ SidewaysScroller(minWidth: 1000) {
+ TablerList(eventsConfig,
+ headerContent: eventsHeader,
+ rowContent: eventsRow,
+ results: report.events)
+ }
+ } else {
+ Spacer()
+ }
+ } else {
+ MapView(layer: $unitReportsViewModel.layer,
+ markers: $unitReportsViewModel.markers,
+ geofences: $unitReportsViewModel.flatGeofences,
+ selected: $unitReportsViewModel.selectedMarker,
+ isReport: true)
+ }
+
+ VStack {
+ HStack {
+ Text("report-period")
+ Spacer()
+ Picker(selection: $unitReportsViewModel.reportPeriod) {
+ Text("period-today").tag(ReportDates.ReportPeriod.today)
+ Text("period-last-24").tag(ReportDates.ReportPeriod.last24)
+ Text("period-yesterday").tag(ReportDates.ReportPeriod.yesterday)
+ Text("period-this-week").tag(ReportDates.ReportPeriod.thisWeek)
+ Text("period-last-7").tag(ReportDates.ReportPeriod.last7)
+ Text("period-this-month").tag(ReportDates.ReportPeriod.thisMonth)
+ Text("period-last-30").tag(ReportDates.ReportPeriod.last30)
+ } label: {
+ switch unitReportsViewModel.reportPeriod {
+ case .today:
+ Text("period-today")
+ case .last24:
+ Text("period-last-24")
+ case .yesterday:
+ Text("period-yesterday")
+ case .thisWeek:
+ Text("period-this-week")
+ case .last7:
+ Text("period-last-7")
+ case .thisMonth:
+ Text("period-this-month")
+ case .last30:
+ Text("period-last-30")
+ default:
+ Text("period-today")
+ }
+ }
+ }.padding()
+ Picker(selection: $unitReportsViewModel.reportType) {
+ Text("report-positions").tag(ReportController.ReportType.positions)
+ Text("report-events").tag(ReportController.ReportType.events)
+ Text("report-stops").tag(ReportController.ReportType.stops)
+ } label: {
+ EmptyView()
+ }.pickerStyle(SegmentedPickerStyle())
+
+ //HStack {
+ // Group {
+ // Button {} label: {
+ // Text("report-save")
+ // }
+ //
+ // Button {} label: {
+ // Text("report-share")
+ // }
+ // }.frame(maxWidth: .infinity)
+ //}.padding()
+ }.padding()
+ }.onAppear {
+ unitReportsViewModel.fetchReport()
+ }
}
}
diff --git a/iosApp/iosApp/Details/Reports/UnitReportsViewModel.swift b/iosApp/iosApp/Details/Reports/UnitReportsViewModel.swift
new file mode 100644
index 0000000..4731a57
--- /dev/null
+++ b/iosApp/iosApp/Details/Reports/UnitReportsViewModel.swift
@@ -0,0 +1,94 @@
+//
+// UnitReportsViewModel.swift
+// iosApp
+//
+// Created by Iván on 13/02/22.
+// Copyright © 2022 orgName. All rights reserved.
+//
+
+import Foundation
+import Combine
+import shared
+
+class UnitReportsViewModel: ObservableObject {
+ @Inject var reportController: ReportController
+ @Inject var geofencesController: GeofencesController
+
+ @Published var deviceId: Int32? = nil
+ @Published var reportType: ReportController.ReportType = .positions {
+ didSet {
+ fetchReport()
+ }
+ }
+ @Published var reportPeriod: ReportDates.ReportPeriod = .today {
+ didSet {
+ fetchReport()
+ }
+ }
+ @Published var report: ReportController.Report? = nil {
+ didSet {
+ switch report {
+ case let report as ReportController.ReportPositionsReport:
+ markers = report.positions.compactMap { p in Marker.companion.fromPosition(position: p)}
+ case is ReportController.ReportEventsReport:
+ markers = []
+ case let report as ReportController.ReportStopsReport:
+ markers = report.stops.compactMap { s in Marker.companion.fromStop(stop: s) }
+ default:
+ break
+ }
+ }
+ }
+ @Published var layer = MapLayer.companion.defaultLayer
+ @Published var markers = [Marker]()
+ @Published var selectedMarker: Marker? = nil
+
+ @Published var geofences: [Int: Geofence] = [:] {
+ didSet {
+ flatGeofences = Array(geofences.values)
+ }
+ }
+ @Published var flatGeofences: [Geofence] = []
+
+ init(deviceId id: Int32?) {
+ deviceId = id
+ let collector = Collector<ReportController.Report>(callback: setReport)
+ reportController.reportFlow.collect(collector: collector) { _, _ in }
+
+ let geofencesCollector = Collector<[Int: Geofence]>(callback: setGeofences)
+ geofencesController.geofencesFlow.collect(collector: geofencesCollector) { unit, error in }
+ }
+
+ func setReport (report: ReportController.Report) {
+ self.report = report
+ }
+
+ private func setGeofences(geofences: [Int: Geofence]) {
+ self.geofences = geofences
+ }
+
+ func fetchReport(xlsx: Bool = false) {
+ if let id = deviceId {
+ reportController.fetchReport(deviceId: id,
+ reportType: reportType,
+ reportPeriod: reportPeriod,
+ xlsx: xlsx, eventTypes: [
+ .deviceInactive,
+ .deviceMoving,
+ .deviceStopped,
+ .deviceOverspeed,
+ .deviceFuelDrop,
+ .commandResult,
+ .geofenceEnter,
+ .geofenceExit,
+ .alarm,
+ .ignitionOn,
+ .ignitionOff,
+ .maintenance,
+ .textMessage,
+ .driverChanged,
+ .unknown
+ ]) { _, _ in }
+ }
+ }
+}
diff --git a/iosApp/iosApp/Devices/DeviceRow.swift b/iosApp/iosApp/Devices/DeviceRow.swift
index 0439fff..ee3298d 100644
--- a/iosApp/iosApp/Devices/DeviceRow.swift
+++ b/iosApp/iosApp/Devices/DeviceRow.swift
@@ -172,9 +172,9 @@ struct DeviceRow: View {
Label("details", systemImage: "info.circle")
}
- //let reports = Button { callback(.reports) } label: {
- // Label("reports", systemImage: "clock")
- //}
+ let reports = Button { callback(.reports) } label: {
+ Label("reports", systemImage: "clock")
+ }
let commands = Button { callback(.commands) } label: {
Label("commands", systemImage: "paperplane")
@@ -182,12 +182,12 @@ struct DeviceRow: View {
if isCell {
commands
- // reports
+ reports
details
} else {
Group {
details
- // reports
+ reports
commands
}
.frame(maxWidth: .infinity)
diff --git a/iosApp/iosApp/Map/MapView.swift b/iosApp/iosApp/Map/MapView.swift
index e52c034..84bdc05 100644
--- a/iosApp/iosApp/Map/MapView.swift
+++ b/iosApp/iosApp/Map/MapView.swift
@@ -25,12 +25,15 @@ struct MapView: UIViewControllerRepresentable {
@Binding var layer: MapLayer
@Binding var markers: [Marker]
+ @Binding var geofences: [Geofence]
@Binding var selected: Marker?
+ var isReport: Bool = false
var markerCallback: MarkerCallback?
class Coordinator {
var shouldCenter: Bool = true
var oldMarkers: [Marker] = []
+ var oldGeofences: [Geofence] = []
}
func makeCoordinator() -> Coordinator {
@@ -50,14 +53,19 @@ struct MapView: UIViewControllerRepresentable {
// MARK: - Set markers
if context.coordinator.oldMarkers != markers {
- print("center = \(context.coordinator.shouldCenter)")
uiViewController.display(markers: markers,
- isReport: false,
+ isReport: isReport,
center: context.coordinator.shouldCenter)
- context.coordinator.shouldCenter = false
+ context.coordinator.shouldCenter = false || isReport
}
context.coordinator.oldMarkers = markers
+ // MARK: - Set geofences
+ if context.coordinator.oldGeofences != geofences {
+ uiViewController.display(geofences: geofences)
+ }
+ context.coordinator.oldGeofences = geofences
+
// MARK: - Center selected marker
if let selected = selected {
uiViewController.focusOn(marker: selected)
diff --git a/iosApp/iosApp/Map/MapViewController.swift b/iosApp/iosApp/Map/MapViewController.swift
index 131a511..3c38b89 100644
--- a/iosApp/iosApp/Map/MapViewController.swift
+++ b/iosApp/iosApp/Map/MapViewController.swift
@@ -18,9 +18,23 @@
import UIKit
import WhirlyGlobe
import shared
+import GEOSwift
typealias MarkerCallback = (Int32?) -> ()
+extension Geometry {
+ func getCoordinates() -> [Point] {
+ switch self {
+ case let .polygon(polygon):
+ return polygon.exterior.points
+ case let .lineString(lineString):
+ return lineString.points
+ default:
+ return []
+ }
+ }
+}
+
class MapViewController: UIViewController {
var markerCallback: MarkerCallback? = nil
var mapLayer: MapLayer = MapLayer.companion.defaultLayer
@@ -84,6 +98,12 @@ class MapViewController: UIViewController {
}
}
+ func display(geofences: [Geofence]) {
+ mapView.runOnInit {
+ self.mapView.display(geofences: geofences)
+ }
+ }
+
func focusOn(marker: Marker) {
mapView.runOnInit {
self.mapView.focusOn(marker: marker)
@@ -205,13 +225,14 @@ class OurMaplyViewController: MaplyViewController {
}
let fontSize = 11.0
- let colorReport = UIColor.green
- let colorLabel = UIColor.secondaryLabel
- let colorLabelOutline = UIColor.systemBackground
+ let colorReport = UIColor(red: 0.0, green: 0.5, blue: 0.0, alpha: 1.0)
+ let colorLabel = UIColor.darkGray
+ let colorLabelOutline = UIColor.white
let vectorDesc: [AnyHashable : Any] = [
kMaplyColor: colorReport,
- kMaplyVecWidth: 20.0
+ kMaplyVecWidth: 15.0,
+ kMaplyWideVecImpl: kMaplyWideVecImplPerf
]
let labelDesc: [AnyHashable : Any] = [
@@ -232,7 +253,7 @@ class OurMaplyViewController: MaplyViewController {
// For reports, position, start and end icons must be different
switch i {
case markers.startIndex: type = .reportStart
- case markers.endIndex: type = .reportEnd
+ case markers.endIndex - 1: type = .reportEnd
default: type = .reportPosition
}
} else {
@@ -245,8 +266,8 @@ class OurMaplyViewController: MaplyViewController {
// 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
+ case markers.endIndex - 1: size = 50.0
+ default: size = 26.0
}
}
screenMarker.size = CGSize(width: size, height: size)
@@ -289,15 +310,19 @@ class OurMaplyViewController: MaplyViewController {
"type": "FeatureCollection",
"features": [
[
- "type": "LineString",
- "coordinates": points.map({ point in
- [point.x, point.y]
- })
+ "type": "Feature",
+ "properties": [],
+ "geometry": [
+ "type": "LineString",
+ "coordinates": markers.map({ marker in
+ [marker.longitude, marker.latitude]
+ })
+ ]
]
]
]
if let vector = MaplyVectorObject(fromGeoJSONDictionary: geoJSON) {
- if let objs = addVectors([vector], desc: vectorDesc, mode: .any) {
+ if let objs = addWideVectors([vector], desc: vectorDesc, mode: .any) {
objects.append(objs)
}
}
@@ -314,6 +339,85 @@ class OurMaplyViewController: MaplyViewController {
}
}
+ func display(geofences: [Geofence]) {
+ clear(geofences: true)
+
+ let fontSize = 11.0
+ let colorFill = UIColor(red: 0.10, green: 0.46, blue: 0.82, alpha: 1.00)
+ let colorLabel = UIColor(red: 0.10, green: 0.46, blue: 0.82, alpha: 1.00)
+ let colorLabelOutline = UIColor.white
+
+ let vectorDesc: [AnyHashable : Any] = [
+ kMaplyColor: colorFill,
+ kMaplyVecWidth: 12.0,
+ kMaplyWideVecImpl: kMaplyWideVecImplPerf
+ ]
+
+ let labelDesc: [AnyHashable : Any] = [
+ kMaplyFont: UIFont.boldSystemFont(ofSize: fontSize),
+ kMaplyTextColor: colorLabel,
+ kMaplyTextOutlineColor: colorLabelOutline,
+ kMaplyTextOutlineSize: 3.0
+ ]
+
+ let shapes = [MaplyShape]()
+ var vectors = [MaplyVectorObject]()
+ var labels = [MaplyScreenLabel]()
+
+ geofences.forEach { geofence in
+ if let area = geofence.area {
+ print ("Draw geofence with area = \(area)")
+ if let geometry = try? Geometry(wkt: area) {
+ switch geometry {
+ case .polygon(_):
+ let geoJSON: [AnyHashable : Any] = [
+ "type": "FeatureCollection",
+ "features": [
+ [
+ "type": "Feature",
+ "properties": [],
+ "geometry": [
+ "type": "Polygon",
+ "coordinates": [
+ geometry.getCoordinates().map { coordinate in
+ [coordinate.y, coordinate.x]
+ }
+ ]
+ ]
+ ]
+ ]
+ ]
+ if let vector = MaplyVectorObject(fromGeoJSONDictionary: geoJSON) {
+ vectors.append(vector)
+ }
+ default:
+ break
+ }
+
+ if let centroid = try? geometry.centroid() {
+ let label = MaplyScreenLabel()
+ label.text = geofence.name
+ label.loc = MaplyCoordinateMakeWithDegrees(Float(centroid.y),
+ Float(centroid.x))
+ label.layoutImportance = .infinity
+
+ labels.append(label)
+ }
+ }
+ }
+ }
+
+ if let objs = addShapes(shapes, desc: nil, mode: .any) {
+ geofenceObjects.append(objs)
+ }
+ if let objs = addWideVectors(vectors, desc: vectorDesc, mode: .any) {
+ geofenceObjects.append(objs)
+ }
+ if let objs = addScreenLabels(labels, desc: labelDesc, mode: .any) {
+ geofenceObjects.append(objs)
+ }
+ }
+
func setZoomLimits(minZoom: Int32, maxZoom: Int32) {
setZoomLimitsMin(
height(forMapScale: Float(truncating:
diff --git a/iosApp/iosApp/Map/UnitMapView.swift b/iosApp/iosApp/Map/UnitMapView.swift
index 533ac13..f416554 100644
--- a/iosApp/iosApp/Map/UnitMapView.swift
+++ b/iosApp/iosApp/Map/UnitMapView.swift
@@ -25,6 +25,7 @@ struct UnitMapView: View {
ZStack {
MapView(layer: $unitsViewModel.mapLayerType,
markers: $unitsViewModel.markers,
+ geofences: $unitsViewModel.flatGeofences,
selected: $unitsViewModel.selectedMarker,
markerCallback: unitsViewModel.selectUnitWith)
if let unit = unitsViewModel.selectedUnit {
diff --git a/iosApp/iosApp/Shared/MarkerTransformations.swift b/iosApp/iosApp/Shared/MarkerTransformations.swift
index 6291823..7291a58 100644
--- a/iosApp/iosApp/Shared/MarkerTransformations.swift
+++ b/iosApp/iosApp/Shared/MarkerTransformations.swift
@@ -43,6 +43,10 @@ class MarkerTransformations {
case .trolleybus: imageName = "MapTrolleybus"
case .truck: imageName = "MapTruck"
case .van: imageName = "MapVan"
+
+ case .reportStart: imageName = "MapReportStart"
+ case .reportPosition: imageName = "MapReportPosition"
+ case .reportEnd: imageName = "MapReportEnd"
default: break
}
return imageName
diff --git a/iosApp/iosApp/Shared/SidewaysScroller.swift b/iosApp/iosApp/Shared/SidewaysScroller.swift
new file mode 100644
index 0000000..262b121
--- /dev/null
+++ b/iosApp/iosApp/Shared/SidewaysScroller.swift
@@ -0,0 +1,42 @@
+//
+// SidewaysScroller.swift
+//
+// Copyright 2022 FlowAllocator LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+import SwiftUI
+
+/// wrap a view in a horizontal scroll view, for display of large tables on compact display area
+public struct SidewaysScroller<Content: View>: View {
+ var minWidth: CGFloat
+ @ViewBuilder var content: () -> Content
+
+ public init(minWidth: CGFloat,
+ @ViewBuilder content: @escaping () -> Content)
+ {
+ self.minWidth = minWidth
+ self.content = content
+ }
+
+ public var body: some View {
+ GeometryReader { geo in
+ ScrollView(.horizontal) {
+ VStack(alignment: .leading) {
+ content()
+ }
+ .frame(minWidth: max(minWidth, geo.size.width))
+ }
+ }
+ }
+}
diff --git a/iosApp/iosApp/Units/UnitsView.swift b/iosApp/iosApp/Units/UnitsView.swift
index 2d51f53..7ddf5aa 100644
--- a/iosApp/iosApp/Units/UnitsView.swift
+++ b/iosApp/iosApp/Units/UnitsView.swift
@@ -78,7 +78,7 @@ struct UnitsView: View {
}
}
.navigationViewStyle(StackNavigationViewStyle())
- .sheet(isPresented: $unitsViewModel.showDetails) {
+ .fullScreenCover(isPresented: $unitsViewModel.showDetails) {
print("Dismissed")
} content: {
DetailsView(isPresented: $unitsViewModel.showDetails,
diff --git a/iosApp/iosApp/Units/UnitsViewModel.swift b/iosApp/iosApp/Units/UnitsViewModel.swift
index 47adeb7..8ba6a4b 100644
--- a/iosApp/iosApp/Units/UnitsViewModel.swift
+++ b/iosApp/iosApp/Units/UnitsViewModel.swift
@@ -60,7 +60,12 @@ class UnitsViewModel: ObservableObject {
}
@Published var selectedMarker: Marker? = nil
@Published var mapLayerType: MapLayer = .companion.defaultLayer
- @Published var geofences: [Int: Geofence] = [:]
+ @Published var geofences: [Int: Geofence] = [:] {
+ didSet {
+ flatGeofences = Array(geofences.values)
+ }
+ }
+ @Published var flatGeofences: [Geofence] = []
init() {
unitsController.fetchUnits(scope: mainScope)
diff --git a/iosApp/iosApp/en.lproj/Localizable.strings b/iosApp/iosApp/en.lproj/Localizable.strings
index 97070ed..ca8e5a1 100644
--- a/iosApp/iosApp/en.lproj/Localizable.strings
+++ b/iosApp/iosApp/en.lproj/Localizable.strings
@@ -54,6 +54,45 @@
"open-location-browser" = "Open location in browser";
"maps-url-template" = "https://www.google.com/maps/place/{y},{x}?z=19";
+"report-period" = "Period";
+"report-positions" = "Positions";
+"report-events" = "Events";
+"report-stops" = "Stops";
+"report-save" = "Save";
+"report-share" = "Share";
+
+"period-today" = "Today";
+"period-last-24" = "Last 24H";
+"period-yesterday" = "Yesterday";
+"period-this-week" = "Week";
+"period-last-7" = "Last 7d";
+"period-this-month" = "Month";
+"period-last-30" = "Last 30d";
+
+"events-table-event" = "Event";
+"events-table-datetime" = "Datetime";
+"events-table-geofence" = "Geofence";
+"events-table-address" = "Address";
+
+"event-device-online" = "Status online";
+"event-device-unknown" = "Status unknown";
+"event-device-offline" = "Status offline";
+"event-device-inactive" = "Device inactive";
+"event-device-moving" = "Device moving";
+"event-device-stopped" = "Device stopped";
+"event-device-overspeed" = "Speed limit exceeded";
+"event-device-fuel-drop" = "Fuel drop";
+"event-command-result" = "Command result";
+"event-geofence-enter" = "Geofence entered";
+"event-geofence-exit" = "Geofence exited";
+"event-alarm" = "Alarm";
+"event-ignition-on" = "Ignition on";
+"event-ignition-off" = "Ignition off";
+"event-maintenance" = "Maintenance required";
+"event-text-message" = "Text message received";
+"event-driver-changed" = "Driver changed";
+"event-unknown" = "Unknown event";
+
"send-command" = "Send command";
"send-command-confirm" = "Are you sure you want to send the command?";
diff --git a/iosApp/iosApp/es-419.lproj/Localizable.strings b/iosApp/iosApp/es-419.lproj/Localizable.strings
index a91787a..9abd96a 100644
--- a/iosApp/iosApp/es-419.lproj/Localizable.strings
+++ b/iosApp/iosApp/es-419.lproj/Localizable.strings
@@ -54,6 +54,45 @@
"open-location-browser" = "Abrir ubicación en navegador";
"maps-url-template" = "https://www.google.com/maps/place/{y},{x}?z=19";
+"report-period" = "Periodo";
+"report-positions" = "Posiciones";
+"report-events" = "Eventos";
+"report-stops" = "Paradas";
+"report-save" = "Guardar";
+"report-share" = "Compartir";
+
+"period-today" = "Hoy";
+"period-last-24" = "Últimas 24H";
+"period-yesterday" = "Ayer";
+"period-this-week" = "Semana";
+"period-last-7" = "Últimos 7d";
+"period-this-month" = "Mes";
+"period-last-30" = "Últimos 30d";
+
+"events-table-event" = "Evento";
+"events-table-datetime" = "Fecha y hora";
+"events-table-geofence" = "Geocerca";
+"events-table-address" = "Dirección";
+
+"event-device-online" = "Unidad en línea";
+"event-device-unknown" = "Unidad en estado desconocido";
+"event-device-offline" = "Unidad fuera de línea";
+"event-device-inactive" = "Unidad inactiva";
+"event-device-moving" = "Unidad en movimiento";
+"event-device-stopped" = "Unidad detenida";
+"event-device-overspeed" = "Excedido el límite de velocidad";
+"event-device-fuel-drop" = "Pérdida de combustible";
+"event-command-result" = "Resultado del comando";
+"event-geofence-enter" = "Entrada en la geocerca";
+"event-geofence-exit" = "Salida de la geocerca";
+"event-alarm" = "Alarma";
+"event-ignition-on" = "Ignición encendida";
+"event-ignition-off" = "Ignición apagada";
+"event-maintenance" = "Se requiere mantenimiento";
+"event-text-message" = "Mensaje de texto recibido";
+"event-driver-changed" = "El conductor ha cambiado";
+"event-unknown" = "Evento desconocido";
+
"send-command" = "Enviar comando";
"send-command-confirm" = "¿Está seguro que desea enviar el comando?";
diff --git a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/ReportController.kt b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/ReportController.kt
index b2e97e6..29c4229 100644
--- a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/ReportController.kt
+++ b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/ReportController.kt
@@ -49,7 +49,7 @@ class ReportController(
val reportFlow: MutableStateFlow<Report> = MutableStateFlow(Report.LoadingReport)
- fun fetchReport(
+ suspend fun fetchReport(
deviceId: Int,
reportType: ReportType?,
reportPeriod: ReportDates.ReportPeriod?,
@@ -65,13 +65,13 @@ class ReportController(
if (!xlsx) {
reportFlow.value = Report.LoadingReport
}
- GlobalScope.launch {
+ // GlobalScope.launch {
when (reportType) {
ReportType.POSITIONS -> fetchPositions(deviceId, previousDate, currentDate, xlsx)
ReportType.EVENTS -> fetchEvents(deviceId, previousDate, currentDate, eventTypes, xlsx)
ReportType.STOPS -> fetchStops(deviceId, previousDate, currentDate, xlsx)
}
- }
+ // }
}
private suspend fun fetchPositions(