diff options
author | Iván Ávalos <avalos@disroot.org> | 2022-02-25 22:02:51 -0600 |
---|---|---|
committer | Iván Ávalos <avalos@disroot.org> | 2022-02-25 22:02:51 -0600 |
commit | 136e4ebe289e286b62c8e37bcd512de6df0de0d3 (patch) | |
tree | 894ce7d172a39b0a7c99e19e9b4d25064549529a | |
parent | ef7a88961841752cb1a38a39f5e0cc298b463f56 (diff) | |
parent | 70141fe10227ef4eca2ef7ae4b2b9d7c8fac5675 (diff) | |
download | etbsa-trackermap-mobile-136e4ebe289e286b62c8e37bcd512de6df0de0d3.tar.gz etbsa-trackermap-mobile-136e4ebe289e286b62c8e37bcd512de6df0de0d3.tar.bz2 etbsa-trackermap-mobile-136e4ebe289e286b62c8e37bcd512de6df0de0d3.zip |
Merge branch 'main' into ios_reports
20 files changed, 438 insertions, 68 deletions
diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index aa509da..abdf916 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -21,7 +21,11 @@ E33A237127A7553500DD647F /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E33A237027A7553500DD647F /* MapView.swift */; }; E33A237327A7581A00DD647F /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = E33A237227A7581A00DD647F /* Utils.swift */; }; E34A2F4827A7878200AD8AEB /* HyperlinkText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E34A2F4727A7878200AD8AEB /* HyperlinkText.swift */; }; - E34A2F4D27A7DB2200AD8AEB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E34A2F4C27A7DB2200AD8AEB /* Localizable.strings */; }; + E360251527BCA3AD00958B21 /* UserInformationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E360251427BCA3AD00958B21 /* UserInformationView.swift */; }; + E360251727BCA83300958B21 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E360251627BCA83300958B21 /* AccountView.swift */; }; + E360251927BCA84000958B21 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E360251827BCA84000958B21 /* AboutView.swift */; }; + E360251B27BCA8A600958B21 /* AccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E360251A27BCA8A600958B21 /* AccountViewModel.swift */; }; + E360251C27BCC97000958B21 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E360251E27BCC97000958B21 /* Localizable.strings */; }; E36A5A8627B4BFC40070DED5 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = E36A5A8527B4BFC40070DED5 /* FirebaseMessaging */; }; E36DF77B27AB740C003C561C /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E36DF77927AB740C003C561C /* MapViewController.swift */; }; E36DF77C27AB740C003C561C /* MapViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E36DF77A27AB740C003C561C /* MapViewController.xib */; }; @@ -74,7 +78,12 @@ 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>"; }; 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>"; }; + E360251427BCA3AD00958B21 /* UserInformationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInformationView.swift; sourceTree = "<group>"; }; + E360251627BCA83300958B21 /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = "<group>"; }; + E360251827BCA84000958B21 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; }; + E360251A27BCA8A600958B21 /* AccountViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewModel.swift; sourceTree = "<group>"; }; + E360251D27BCC97000958B21 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; }; + E360251F27BCC97300958B21 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; }; E36A5A8927B4C8BB0070DED5 /* iosApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iosApp.entitlements; 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>"; }; @@ -140,7 +149,7 @@ children = ( E392BE1827B791F0002698F3 /* GoogleService-Info.plist */, E36A5A8927B4C8BB0070DED5 /* iosApp.entitlements */, - E34A2F4C27A7DB2200AD8AEB /* Localizable.strings */, + E360251E27BCC97000958B21 /* Localizable.strings */, E33A235E27A4FD1C00DD647F /* Map */, E35A078427AB615F00F24D71 /* Details */, E39ABC4427A4EBB000965D05 /* Devices */, @@ -236,6 +245,10 @@ children = ( 7555FF82242A565900829871 /* RootView.swift */, E38F241B27A26DD70069FC45 /* RootViewModel.swift */, + E360251427BCA3AD00958B21 /* UserInformationView.swift */, + E360251627BCA83300958B21 /* AccountView.swift */, + E360251827BCA84000958B21 /* AboutView.swift */, + E360251A27BCA8A600958B21 /* AccountViewModel.swift */, ); path = Session; sourceTree = "<group>"; @@ -326,7 +339,7 @@ buildActionMask = 2147483647; files = ( 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */, - E34A2F4D27A7DB2200AD8AEB /* Localizable.strings in Resources */, + E360251C27BCC97000958B21 /* Localizable.strings in Resources */, E392BE1927B791F0002698F3 /* GoogleService-Info.plist in Resources */, 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */, E36DF77C27AB740C003C561C /* MapViewController.xib in Resources */, @@ -366,6 +379,9 @@ E38F241727A242C70069FC45 /* Resolver.swift in Sources */, E33A236027A4FD2C00DD647F /* UnitMapView.swift in Sources */, E396282427AFBD3D005D070E /* UnitInformationView.swift in Sources */, + E360251727BCA83300958B21 /* AccountView.swift in Sources */, + E360251527BCA3AD00958B21 /* UserInformationView.swift in Sources */, + E360251927BCA84000958B21 /* AboutView.swift in Sources */, E312E74F27B366060018C5DE /* DevicesViewModel.swift in Sources */, E33A236727A64E4500DD647F /* MarkerTransformations.swift in Sources */, E38F241527A242870069FC45 /* Inject.swift in Sources */, @@ -379,6 +395,7 @@ E3E77EE6279E6CE400150070 /* FlowCollector.swift in Sources */, E33A236527A530F300DD647F /* SmallLabelStyle.swift in Sources */, E39ABC4327A4E88C00965D05 /* UnitsViewModel.swift in Sources */, + E360251B27BCA8A600958B21 /* AccountViewModel.swift in Sources */, E34A2F4827A7878200AD8AEB /* HyperlinkText.swift in Sources */, 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, E39ABC4627A4EBD500965D05 /* DevicesView.swift in Sources */, @@ -391,6 +408,18 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXVariantGroup section */ + E360251E27BCC97000958B21 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + E360251D27BCC97000958B21 /* en */, + E360251F27BCC97300958B21 /* es-419 */, + ); + name = Localizable.strings; + sourceTree = "<group>"; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ 7555FFA3242A565B00829871 /* Debug */ = { isa = XCBuildConfiguration; diff --git a/iosApp/iosApp/AppDelegate.swift b/iosApp/iosApp/AppDelegate.swift index 4233910..72e3e92 100644 --- a/iosApp/iosApp/AppDelegate.swift +++ b/iosApp/iosApp/AppDelegate.swift @@ -39,6 +39,14 @@ class AppDelegate: NSObject, UIApplicationDelegate { print(userInfo) return UIBackgroundFetchResult.newData } + + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + #if DEBUG + Messaging.messaging().setAPNSToken(deviceToken, type: .sandbox) + #else + Messaging.messaging().setAPNSToken(deviceToken, type: .prod) + #endif + } } extension AppDelegate: UNUserNotificationCenterDelegate { diff --git a/iosApp/iosApp/Details/Commands/UnitCommandsViewModel.swift b/iosApp/iosApp/Details/Commands/UnitCommandsViewModel.swift index d7298c4..8e1da00 100644 --- a/iosApp/iosApp/Details/Commands/UnitCommandsViewModel.swift +++ b/iosApp/iosApp/Details/Commands/UnitCommandsViewModel.swift @@ -21,6 +21,7 @@ import shared class UnitCommandsViewModel: ObservableObject { @Inject var commandsController: CommandsController + var deviceId: Int32? = nil @Published var commands = [Command]() @Published var selected: Command? @Published var selectedId: Int = 0 { @@ -29,6 +30,9 @@ class UnitCommandsViewModel: ObservableObject { Int(truncating: $0.id!) == selectedId }) { self.selected = selected + if let id = deviceId { + self.selected?.deviceId = KotlinInt(int: id) + } notSelected = false } else { self.selected = nil @@ -40,6 +44,7 @@ class UnitCommandsViewModel: ObservableObject { @Published var showConfirmation: Bool = false func fetchCommands(id: Int32) { + deviceId = id commandsController.fetchCommands(deviceId: id) { commands, error in print("We've got the commands! \(commands ?? [])") self.commands = commands ?? [] @@ -48,7 +53,12 @@ class UnitCommandsViewModel: ObservableObject { func sendCommand() { if let command = selected { - commandsController.sendCommand(command: command) { _, error in } + commandsController.sendCommand(command: command) { _, error in + if let error = error { + print("There is a fucking error") + print(error.localizedDescription) + } + } } } } diff --git a/iosApp/iosApp/Details/DetailsView.swift b/iosApp/iosApp/Details/DetailsView.swift index 2c7418b..c869369 100644 --- a/iosApp/iosApp/Details/DetailsView.swift +++ b/iosApp/iosApp/Details/DetailsView.swift @@ -40,6 +40,8 @@ struct DetailsView: View { UnitReportsView(unit: unit) case .commands: UnitCommandsView(unit: unit) + default: + EmptyView() } } else { LoadingView() @@ -48,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() @@ -62,6 +64,7 @@ struct DetailsView: View { let type = Marker.companion .categoryToMarkerType(category: category) Image(MarkerTransformations.markerTypeToImageName(markerType: type)) + .sizeToFit() } else { EmptyView() } diff --git a/iosApp/iosApp/Devices/DeviceRow.swift b/iosApp/iosApp/Devices/DeviceRow.swift index 4553a4b..0439fff 100644 --- a/iosApp/iosApp/Devices/DeviceRow.swift +++ b/iosApp/iosApp/Devices/DeviceRow.swift @@ -25,6 +25,7 @@ struct DeviceRow: View { case details case reports case commands + case close } var callback: (Action) -> () var isCell: Bool = true @@ -37,6 +38,7 @@ struct DeviceRow: View { /* MARK: - Device icon */ let category = Marker.companion.categoryToMarkerType(category: unit.device.category) Image(MarkerTransformations.markerTypeToImageName(markerType: category)) + .sizeToFitSquare(sideLength: 40.0) .padding(5.0) getSharedContent() @@ -98,6 +100,16 @@ struct DeviceRow: View { /* MARK: - Device name */ Text(unit.device.name) Spacer() + + /* MARK: - Close button */ + if !isCell { + Button { + callback(.close) + } label: { + Image(systemName: "xmark") + .foregroundColor(.secondary) + } + } } .padding(.bottom, 5.0) @@ -160,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") @@ -170,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/Info.plist b/iosApp/iosApp/Info.plist index bf9db74..99c8b7b 100644 --- a/iosApp/iosApp/Info.plist +++ b/iosApp/iosApp/Info.plist @@ -4,10 +4,6 @@ <dict> <key>CFBundleDevelopmentRegion</key> <string>$(DEVELOPMENT_LANGUAGE)</string> - <key>UIBackgroundModes</key> - <array> - <string>remote-notification</string> - </array> <key>CFBundleDisplayName</key> <string>TrackerMap</string> <key>CFBundleExecutable</key> @@ -31,6 +27,10 @@ <key>UIApplicationSupportsMultipleScenes</key> <false/> </dict> + <key>UIBackgroundModes</key> + <array> + <string>remote-notification</string> + </array> <key>UILaunchScreen</key> <dict/> <key>UIRequiredDeviceCapabilities</key> diff --git a/iosApp/iosApp/Map/MapView.swift b/iosApp/iosApp/Map/MapView.swift index f9757f4..e52c034 100644 --- a/iosApp/iosApp/Map/MapView.swift +++ b/iosApp/iosApp/Map/MapView.swift @@ -28,8 +28,6 @@ struct MapView: UIViewControllerRepresentable { @Binding var selected: Marker? var markerCallback: MarkerCallback? - var shouldCenter = true - class Coordinator { var shouldCenter: Bool = true var oldMarkers: [Marker] = [] @@ -52,11 +50,12 @@ struct MapView: UIViewControllerRepresentable { // MARK: - Set markers if context.coordinator.oldMarkers != markers { + print("center = \(context.coordinator.shouldCenter)") uiViewController.display(markers: markers, isReport: false, center: context.coordinator.shouldCenter) + context.coordinator.shouldCenter = false } - context.coordinator.shouldCenter = false context.coordinator.oldMarkers = markers // MARK: - Center selected marker diff --git a/iosApp/iosApp/Map/MapViewController.swift b/iosApp/iosApp/Map/MapViewController.swift index bc793e2..131a511 100644 --- a/iosApp/iosApp/Map/MapViewController.swift +++ b/iosApp/iosApp/Map/MapViewController.swift @@ -77,7 +77,7 @@ class MapViewController: UIViewController { func display(markers: [Marker], isReport: Bool, center: Bool = false) { - DispatchQueue.main.async { + mapView.runOnInit { self.mapView.display(markers: markers, isReport: isReport, center: center) @@ -85,7 +85,7 @@ class MapViewController: UIViewController { } func focusOn(marker: Marker) { - DispatchQueue.main.async { + mapView.runOnInit { self.mapView.focusOn(marker: marker) } } diff --git a/iosApp/iosApp/Map/MapViewController.xib b/iosApp/iosApp/Map/MapViewController.xib index 9d1694f..305a15b 100644 --- a/iosApp/iosApp/Map/MapViewController.xib +++ b/iosApp/iosApp/Map/MapViewController.xib @@ -35,6 +35,11 @@ <imageReference key="image" image="plus" catalog="system" symbolScale="large"/> <preferredSymbolConfiguration key="preferredSymbolConfiguration"/> </state> + <userDefinedRuntimeAttributes> + <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius"> + <integer key="value" value="10"/> + </userDefinedRuntimeAttribute> + </userDefinedRuntimeAttributes> <connections> <action selector="onZoomInPressed:" destination="-1" eventType="touchUpInside" id="x4P-sk-jdi"/> </connections> @@ -54,6 +59,11 @@ <imageReference key="image" image="minus" catalog="system" symbolScale="large"/> <preferredSymbolConfiguration key="preferredSymbolConfiguration"/> </state> + <userDefinedRuntimeAttributes> + <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius"> + <integer key="value" value="10"/> + </userDefinedRuntimeAttribute> + </userDefinedRuntimeAttributes> <connections> <action selector="onZoomOutPressed:" destination="-1" eventType="touchUpInside" id="rt2-fy-VCy"/> </connections> diff --git a/iosApp/iosApp/Session/AboutView.swift b/iosApp/iosApp/Session/AboutView.swift new file mode 100644 index 0000000..7e5325b --- /dev/null +++ b/iosApp/iosApp/Session/AboutView.swift @@ -0,0 +1,48 @@ +// +// AboutView.swift +// iosApp +// +// Created by Iván on 15/02/22. +// Copyright © 2022 orgName. All rights reserved. +// + +import SwiftUI + +struct AboutView: View { + + private func getVersion() -> String? { + return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + } + + var body: some View { + List { + Text("about-text").padding() + if let version = getVersion() { + HStack { + Text("version") + Spacer() + Text(version).foregroundColor(.secondary) + } + } + + Button { + if let url = URL(string: NSLocalizedString("app-source-code", comment: "")) { + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:]) + } + } + } label: { + Label("source-code", systemImage: "chevron.left.forwardslash.chevron.right") + } + + Button { + if let url = URL(string: NSLocalizedString("app-website", comment: "")) { + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:]) + } + } } label: { + Label("website", systemImage: "globe") + } + } + } +} diff --git a/iosApp/iosApp/Session/AccountView.swift b/iosApp/iosApp/Session/AccountView.swift new file mode 100644 index 0000000..4ca453a --- /dev/null +++ b/iosApp/iosApp/Session/AccountView.swift @@ -0,0 +1,73 @@ +// +// AccountView.swift +// iosApp +// +// Created by Iván on 15/02/22. +// Copyright © 2022 orgName. All rights reserved. +// + +import SwiftUI + +struct AccountView: View { + @StateObject var accountViewModel = AccountViewModel() + @EnvironmentObject var rootViewModel: RootViewModel + + var body: some View { + List { + Section { + // MARK: - Name + if let name = accountViewModel.user?.name { + HStack { + Text("username") + Spacer() + Text(name).foregroundColor(.secondaryLabel) + } + } + // MARK: - E-mail + if let email = accountViewModel.user?.email { + HStack { + Text("email") + Spacer() + Text(email).foregroundColor(.secondaryLabel) + } + } + // MARK: - Unique ID + if let uid = accountViewModel.user?.id { + HStack { + Text("unique-id") + Spacer() + Text("\(uid)").foregroundColor(.secondaryLabel) + } + } + // MARK: - Administrator + if let admin = accountViewModel.user?.administrator { + HStack { + Text("admin") + Spacer() + Text("\(admin)").foregroundColor(.secondaryLabel) + } + } + // MARK: - Server URL + if let server = UserDefaults.standard.string(forKey: "server-url") { + HStack { + Text("server-url") + Spacer() + Text(server).foregroundColor(.secondaryLabel) + } + } + } + + Section { + // MARK: - Sign out + Button { + rootViewModel.signOut() + } label: { + Label("signout", systemImage: "rectangle.portrait.and.arrow.right") + .foregroundColor(.systemRed) + } + } + }.onAppear { + accountViewModel.fetchUserInfo() + } + } +} diff --git a/iosApp/iosApp/Session/AccountViewModel.swift b/iosApp/iosApp/Session/AccountViewModel.swift new file mode 100644 index 0000000..7953265 --- /dev/null +++ b/iosApp/iosApp/Session/AccountViewModel.swift @@ -0,0 +1,29 @@ +// +// AccountViewModel.swift +// iosApp +// +// Created by Iván on 15/02/22. +// Copyright © 2022 orgName. All rights reserved. +// + +import Foundation +import shared + +class AccountViewModel: ObservableObject { + @Inject var sessionController: SessionController + + @Published var user: User? = nil + + init() { + let userCollector = Collector<User?>(callback: setUser) + sessionController.userFlow.collect(collector: userCollector) {_, _ in } + } + + func setUser(user: User?) { + self.user = user + } + + func fetchUserInfo() { + self.sessionController.getSession() + } +} diff --git a/iosApp/iosApp/Session/RootView.swift b/iosApp/iosApp/Session/RootView.swift index bc8a7d1..297a2aa 100644 --- a/iosApp/iosApp/Session/RootView.swift +++ b/iosApp/iosApp/Session/RootView.swift @@ -18,24 +18,35 @@ import SwiftUI import shared +class SessionState: ObservableObject { + @Published var loggedIn = false +} + struct RootView: View { @StateObject private var rootViewModel = RootViewModel() @State private var username = "" @State private var password = "" - @State private var server = "" + @State private var server: String + + init() { + server = UserDefaults.standard.string(forKey: "server-url") + ?? NSLocalizedString("app-server-url", comment: "") + } var body: some View { - switch rootViewModel.loginState { - case is SessionController.LoginStateLoading: - LoadingView() - case is SessionController.LoginStateSuccess: - UnitsView() - default: - LoginContentView(username: $username, - password: $password, - server: $server, - onLogin: rootViewModel.login) - } + Group { + switch rootViewModel.loginState { + case is SessionController.LoginStateLoading: + LoadingView() + case is SessionController.LoginStateSuccess: + UnitsView() + default: + LoginContentView(username: $username, + password: $password, + server: $server, + onLogin: rootViewModel.login) + } + }.environmentObject(rootViewModel) } } @@ -44,13 +55,7 @@ struct LoginContentView: View { @Binding var password: String @Binding var server: String - let onLogin: (SessionBody) -> Void - - func getFcmToken() -> String? { - let token = UserDefaults.standard.string(forKey: "fcmtoken") - print("FCM token is \(String(describing: token))") - return token - } + let onLogin: (String, String, String) -> Void var body: some View { VStack { @@ -63,10 +68,7 @@ struct LoginContentView: View { server: $server) Button(action: { - self.onLogin(SessionBody(url: server, - email: username, - password: password, - fcmToken: getFcmToken())) + self.onLogin(username, password, server) }) { Text("login") .font(.system(size: 18)) diff --git a/iosApp/iosApp/Session/RootViewModel.swift b/iosApp/iosApp/Session/RootViewModel.swift index d065777..682deee 100644 --- a/iosApp/iosApp/Session/RootViewModel.swift +++ b/iosApp/iosApp/Session/RootViewModel.swift @@ -38,10 +38,23 @@ class RootViewModel: ObservableObject { sessionController.restoreSession() } - func login(session: SessionBody) { - print("Username: \(session.email)") - print("Password: \(session.password)") - print("Server URL: \(session.url)") - sessionController.login(body: session) + private func getFcmToken() -> String? { + let token = UserDefaults.standard.string(forKey: "fcmtoken") + print("FCM token is \(String(describing: token))") + return token + } + + func login(username: String, password: String, url: String) { + print("Username: \(username)") + print("Password: \(password)") + print("Server URL: \(url)") + sessionController.login(body: SessionBody(url: url, + email: username, + password: password, + fcmToken: getFcmToken())) + } + + func signOut() { + sessionController.logout(token: getFcmToken()) } } diff --git a/iosApp/iosApp/Session/UserInformationView.swift b/iosApp/iosApp/Session/UserInformationView.swift new file mode 100644 index 0000000..98d4bfd --- /dev/null +++ b/iosApp/iosApp/Session/UserInformationView.swift @@ -0,0 +1,50 @@ +// +// UserInformationView.swift +// iosApp +// +// Created by Iván on 15/02/22. +// Copyright © 2022 orgName. All rights reserved. +// + +import SwiftUI + +struct UserInformationView: View { + @Environment(\.presentationMode) var presentationMode + @State var action = Action.account + + enum Action { + case account + case about + } + + var body: some View { + NavigationView { + VStack { + switch action { + case .account: + AccountView() + case .about: + AboutView() + } + } + .navigationBarTitleView( + Picker(selection: $action) { + Text("account").tag(Action.account) + Text("about").tag(Action.about) + } label: { + EmptyView() + }.pickerStyle(SegmentedPickerStyle()) + ) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + presentationMode.wrappedValue.dismiss() + } label: { + Text("done").bold() + } + } + } + } + } +} diff --git a/iosApp/iosApp/Units/UnitsView.swift b/iosApp/iosApp/Units/UnitsView.swift index d813c6d..a91511e 100644 --- a/iosApp/iosApp/Units/UnitsView.swift +++ b/iosApp/iosApp/Units/UnitsView.swift @@ -67,7 +67,9 @@ struct UnitsView: View { .visible(unitsViewModel.unitsDisplayMode == .map) } ToolbarItem(placement: .navigationBarTrailing) { - Button(action: {}) { + Button { + unitsViewModel.showUserInfo = true + } label: { Image(systemName: "person") } } @@ -81,6 +83,12 @@ struct UnitsView: View { action: unitsViewModel.detailsAction, for: unitsViewModel.detailsUnit) } + .sheet(isPresented: $unitsViewModel.showUserInfo) { + print("Dismissed") + } content: { + UserInformationView() + } + } private func getNavigationTitle(_ unitDisplayMode: UnitsViewModel.UnitsDisplayMode) -> LocalizedStringKey { diff --git a/iosApp/iosApp/Units/UnitsViewModel.swift b/iosApp/iosApp/Units/UnitsViewModel.swift index 7ffcfd1..47adeb7 100644 --- a/iosApp/iosApp/Units/UnitsViewModel.swift +++ b/iosApp/iosApp/Units/UnitsViewModel.swift @@ -30,17 +30,6 @@ class UnitsViewModel: ObservableObject { case list } - class Camera { - let point: MaplyCoordinate? - let height: Float? - - init(_ point: MaplyCoordinate? = nil, - height: Float? = nil) { - self.point = point - self.height = height - } - } - var detailsUnit: UnitInformation? = nil var detailsAction = DeviceRow.Action.details @@ -52,7 +41,8 @@ class UnitsViewModel: ObservableObject { } @Published var isEditing = false @Published var showDetails = false - @Published var unitsDisplayMode: UnitsDisplayMode = .list + @Published var showUserInfo = false + @Published var unitsDisplayMode: UnitsDisplayMode = .map @Published var units: [UnitInformation] = [] { didSet { markers = units.compactMap(Marker.companion.fromUnit) @@ -71,7 +61,6 @@ class UnitsViewModel: ObservableObject { @Published var selectedMarker: Marker? = nil @Published var mapLayerType: MapLayer = .companion.defaultLayer @Published var geofences: [Int: Geofence] = [:] - @Published var camera: Camera = Camera() init() { unitsController.fetchUnits(scope: mainScope) @@ -127,8 +116,12 @@ class UnitsViewModel: ObservableObject { } func show(action: DeviceRow.Action, for unit: UnitInformation) { - detailsAction = action - detailsUnit = unit - showDetails = true + if action != .close { + detailsAction = action + detailsUnit = unit + showDetails = true + } else { + selectedUnit = nil + } } } diff --git a/iosApp/iosApp/Localizable.strings b/iosApp/iosApp/en.lproj/Localizable.strings index 76e7bd4..392bc1c 100644 --- a/iosApp/iosApp/Localizable.strings +++ b/iosApp/iosApp/en.lproj/Localizable.strings @@ -16,7 +16,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ "app-name" = "TrackerMap"; -"app-server-url" = "https://etbsa.net/api"; +"app-server-url" = "https://gps.trackermap.mx/api"; +"app-website" = "https://trackermap.mx"; +"app-source-code" = "https://git.sr.ht/~avalos/trackermap-mobile"; "loading" = "Loading"; "done" = "Done"; @@ -53,3 +55,14 @@ "send-command" = "Send command"; "send-command-confirm" = "Are you sure you want to send the command?"; + +"account" = "Account"; +"about" = "About"; +"email" = "E-mail"; +"admin" = "Administrator"; +"signout" = "Sign out"; + +"about-text" = "TrackerMap is a free (as in freedom) software app for tracking and managing GPS devices in Traccar servers.\n\nTrackerMap source code is licensed under the GNU General Public License Version 3."; +"version" = "Version"; +"website" = "Website"; +"source-code" = "Source code"; diff --git a/iosApp/iosApp/es-419.lproj/Localizable.strings b/iosApp/iosApp/es-419.lproj/Localizable.strings new file mode 100644 index 0000000..401582e --- /dev/null +++ b/iosApp/iosApp/es-419.lproj/Localizable.strings @@ -0,0 +1,68 @@ +/** + * 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/>. + */ +"app-name" = "TrackerMap"; +"app-server-url" = "https://gps.trackermap.mx/api"; +"app-website" = "https://trackermap.mx"; +"app-source-code" = "https://git.sr.ht/~avalos/trackermap-mobile"; + +"loading" = "Cargando"; +"done" = "Hecho"; + +"username" = "Usuario"; +"password" = "Contraseña"; +"server-url" = "URL del servidor"; +"login" = "Iniciar sesión"; + +"devices" = "Dispositivos"; +"map" = "Mapa"; +"search" = "Buscar"; + +"details" = "Detalles"; +"reports" = "Reportes"; +"commands" = "Comandos"; +"select-action" = "Seleccionar una acción"; + +"map-layer" = "Capa del mapa"; +"openstreetmap" = "OpenStreetMap"; +"satellite" = "Satélite"; + +"contact" = "Contacto"; +"unique-id" = "ID único"; +"datetime" = "Fecha y hora"; +"latitude" = "Latitud"; +"longitude" = "Longitud"; +"speed" = "Velocidad"; +"address" = "Dirección"; +"hourmeter" = "Horómetro"; +"protocol" = "Protocolo"; +"open-location-browser" = "Abrir ubicación en navegador"; +"maps-url-template" = "https://www.google.com/maps/place/{y},{x}?z=19"; + +"send-command" = "Enviar comando"; +"send-command-confirm" = "¿Está seguro que desea enviar el comando?"; + +"account" = "Cuenta"; +"about" = "Acerca de"; +"email" = "Correo electrónico"; +"admin" = "Administrador"; +"signout" = "Cerrar sesión"; + +"about-text" = "TrackerMap es una aplicación de software libre para rastrear y gestionar dispositivos GPS en servidores de Traccar.\n\nEl código fuente de TrackerMap está disponible bajo la licencia GNU General Public License versión 3."; +"version" = "Versión"; +"website" = "Sitio web"; +"source-code" = "Código fuente"; diff --git a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/SessionController.kt b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/SessionController.kt index d3deca1..0faaa72 100644 --- a/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/SessionController.kt +++ b/shared/src/commonMain/kotlin/mx/trackermap/TrackerMap/controllers/SessionController.kt @@ -113,7 +113,7 @@ class SessionController( try { userFlow.value?.let { user -> val attributes = user.attributes.toMutableMap() - attributes["notificationTokens"].toString().let { tokens -> + attributes["notificationTokens"]?.content?.let { tokens -> if (tokens == "null" || !tokens.contains(token)) { if (tokens == "null") { attributes["notificationTokens"] = JsonPrimitive(token) @@ -123,6 +123,8 @@ class SessionController( } + } ?: run { + attributes["notificationTokens"] = JsonPrimitive(token) } usersApi.usersIdPut(user.copy( attributes = attributes @@ -138,7 +140,7 @@ class SessionController( try { userFlow.value?.let { user -> val attributes = user.attributes.toMutableMap() - attributes["notificationTokens"].toString().let { tokens -> + attributes["notificationTokens"]?.content?.let { tokens -> if (tokens.contains("$token,")) { attributes["notificationTokens"] = JsonPrimitive(token.replace("$token,", "")) |