diff --git a/medmap/Assets.xcassets/corporateLogos.imageset/Contents.json b/medmap/Assets.xcassets/corporateLogos.imageset/Contents.json new file mode 100644 index 0000000..cdef1a0 --- /dev/null +++ b/medmap/Assets.xcassets/corporateLogos.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "corporateLogos.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/medmap/Assets.xcassets/corporateLogos.imageset/corporateLogos.svg b/medmap/Assets.xcassets/corporateLogos.imageset/corporateLogos.svg new file mode 100644 index 0000000..06894d1 --- /dev/null +++ b/medmap/Assets.xcassets/corporateLogos.imageset/corporateLogos.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/medmap/Assets.xcassets/splashImage.imageset/Contents.json b/medmap/Assets.xcassets/splashImage.imageset/Contents.json new file mode 100644 index 0000000..f162b5c --- /dev/null +++ b/medmap/Assets.xcassets/splashImage.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "splashImage.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/medmap/Assets.xcassets/splashImage.imageset/splashImage.png b/medmap/Assets.xcassets/splashImage.imageset/splashImage.png new file mode 100644 index 0000000..04290cd Binary files /dev/null and b/medmap/Assets.xcassets/splashImage.imageset/splashImage.png differ diff --git a/medmap/Assets.xcassets/splashLogo.imageset/Contents.json b/medmap/Assets.xcassets/splashLogo.imageset/Contents.json new file mode 100644 index 0000000..7d6821e --- /dev/null +++ b/medmap/Assets.xcassets/splashLogo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "splashLogo.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/medmap/Assets.xcassets/splashLogo.imageset/splashLogo.svg b/medmap/Assets.xcassets/splashLogo.imageset/splashLogo.svg new file mode 100644 index 0000000..c72453a --- /dev/null +++ b/medmap/Assets.xcassets/splashLogo.imageset/splashLogo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/medmap/Extensions/Color+Extensions.swift b/medmap/Extensions/Color+Extensions.swift new file mode 100644 index 0000000..ea62737 --- /dev/null +++ b/medmap/Extensions/Color+Extensions.swift @@ -0,0 +1,63 @@ +// +// Color+Extensions.swift +// medmap +// +// Created by RSA on 28/08/2026. +// + +import SwiftUI + +// MARK: - Color + Hex Initializer +extension Color { + init(hex: String) { + let hexString = hex.trimmingCharacters(in: .whitespacesAndNewlines) + .replacingOccurrences(of: "#", with: "") + + var rgbValue: UInt64 = 0 + Scanner(string: hexString).scanHexInt64(&rgbValue) + + let length = hexString.count + + let r, g, b, a: Double + + switch length { + case 6: // RRGGBB + r = Double((rgbValue & 0xFF0000) >> 16) / 255.0 + g = Double((rgbValue & 0x00FF00) >> 8) / 255.0 + b = Double(rgbValue & 0x0000FF) / 255.0 + a = 1.0 + case 8: // RRGGBBAA + r = Double((rgbValue & 0xFF000000) >> 24) / 255.0 + g = Double((rgbValue & 0x00FF0000) >> 16) / 255.0 + b = Double((rgbValue & 0x0000FF00) >> 8) / 255.0 + a = Double(rgbValue & 0x000000FF) / 255.0 + default: + r = 0 + g = 0 + b = 0 + a = 1.0 + } + + self.init(.sRGB, red: r, green: g, blue: b, opacity: a) + } +} + +// MARK: - Custom App Colors +extension Color { + static let appBackground = Color(hex: "#4078BC") + static let appBackgroundDark = Color(hex: "##4078BC") + + + + + + + static let appSecondary = Color(hex: "#F7F7F8") + static let appAccent = Color(hex: "#4A2EC2") + static let appTextPrimary = Color(hex: "#333333") + static let appBorder = Color(hex: "#E5E5E5") + + // Add more as needed, e.g.: + // static let appError = Color(hex: "#FF3B30") + // static let appSuccess = Color(hex: "#34C759") +} diff --git a/medmap/Extensions/Data+Extensions.swift b/medmap/Extensions/Data+Extensions.swift new file mode 100644 index 0000000..beda99e --- /dev/null +++ b/medmap/Extensions/Data+Extensions.swift @@ -0,0 +1,86 @@ +// +// Data+Extensions.swift +// FluxRead +// +// Created by RSA on 15/02/2026. +// + +import Foundation + +extension Data { + + init(copyingBufferOf array: [T]) { + self = array.withUnsafeBufferPointer(Data.init) + } + + func toArray(type: T.Type) -> [T] where T: ExpressibleByIntegerLiteral { + var array = [T](repeating: 0, count: self.count / MemoryLayout.stride) + _ = array.withUnsafeMutableBytes { copyBytes(to: $0) } + return array + } + + /// Saves the Data object to a file at the specified URL + /// - Parameters: + /// - url: The destination file URL + /// - options: Writing options (default: .atomic) + /// - Throws: Error if writing fails + func saveToFile(at url: URL, options: WritingOptions = .atomic) throws { + try self.write(to: url, options: options) + + } + + /// Saves the Data object to a file with a specified filename in a directory + /// - Parameters: + /// - filename: The name of the file to create + /// - directory: The directory URL where the file should be saved + /// - options: Writing options (default: .atomic) + /// - Returns: The URL of the saved file + /// - Throws: Error if writing fails + @discardableResult + func saveToFile(filename: String, in directory: URL, options: WritingOptions = .atomic) throws -> URL { + let fileURL = directory.appendingPathComponent(filename) + try self.write(to: fileURL, options: options) + + return fileURL + } + + /// Saves the Data object to the app's Documents directory + /// - Parameters: + /// - filename: The name of the file to create + /// - options: Writing options (default: .atomic) + /// - Returns: The URL of the saved file + /// - Throws: Error if writing fails or Documents directory not accessible + @discardableResult + func saveToDocuments(filename: String, options: WritingOptions = .atomic) throws -> URL { + guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + throw NSError(domain: "DataExtension", code: -1, userInfo: [NSLocalizedDescriptionKey: "Could not access Documents directory"]) + } + return try saveToFile(filename: filename, in: documentsDirectory, options: options) + } + + /// Saves the Data object to the app's temporary directory + /// - Parameters: + /// - filename: The name of the file to create + /// - options: Writing options (default: .atomic) + /// - Returns: The URL of the saved file + /// - Throws: Error if writing fails + @discardableResult + func saveToTemporary(filename: String, options: WritingOptions = .atomic) throws -> URL { + let temporaryDirectory = FileManager.default.temporaryDirectory + return try saveToFile(filename: filename, in: temporaryDirectory, options: options) + } + + /// Saves the Data object to the app's Caches directory + /// - Parameters: + /// - filename: The name of the file to create + /// - options: Writing options (default: .atomic) + /// - Returns: The URL of the saved file + /// - Throws: Error if writing fails or Caches directory not accessible + @discardableResult + func saveToCaches(filename: String, options: WritingOptions = .atomic) throws -> URL { + guard let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else { + throw NSError(domain: "DataExtension", code: -1, userInfo: [NSLocalizedDescriptionKey: "Could not access Caches directory"]) + } + return try saveToFile(filename: filename, in: cachesDirectory, options: options) + } +} diff --git a/medmap/Extensions/Date+extensions.swift b/medmap/Extensions/Date+extensions.swift new file mode 100644 index 0000000..fa32128 --- /dev/null +++ b/medmap/Extensions/Date+extensions.swift @@ -0,0 +1,22 @@ +// +// Date+extensions.swift +// FluxRead +// +// Created by RSA on 30/11/2025. +// + +import Foundation + +extension Date { + static func firstDayOfMonth(year: Int, month: Int, calendar: Calendar = .current) -> Date? { + var components = DateComponents() + components.year = year + components.month = month + components.day = 1 + components.hour = 0 + components.minute = 0 + components.second = 0 + + return calendar.date(from: components) + } +} diff --git a/medmap/Extensions/Font+CustomFonts.swift b/medmap/Extensions/Font+CustomFonts.swift new file mode 100755 index 0000000..71448db --- /dev/null +++ b/medmap/Extensions/Font+CustomFonts.swift @@ -0,0 +1,49 @@ +// Copyright © 2024 VOID Software SA. All rights reserved. + +import SwiftUI + +extension Font { + + static let jakartaRegularName = "Plus Jakarta Sans Regular" + static let jakartaBold = "Plus Jakarta Sans Bold" + static let jakartaLight = "Plus Jakarta Sans Light" + static let jakartaMedium = "Plus Jakarta Sans Medium" + + static let interRegular = "Inter Regular" + static let interBold = "Inter Bold" + + static let redHatSemibold = "Red Hat Display SemiBold" + static let redHat = "Red Hat Display Regular" + + static let jakartaRegular24 = Font.custom(jakartaRegularName, size: 24) + static let jakartaRegular64 = Font.custom(jakartaRegularName, size: 64) + static let jakartaRegular60 = Font.custom(jakartaRegularName, size: 60) + static let jakartaRegular56 = Font.custom(jakartaRegularName, size: 56) + static let jakartaRegular16 = Font.custom(jakartaRegularName, size: 16) + static let jakartaRegular14 = Font.custom(jakartaRegularName, size: 14) + static let jakartaRegular12 = Font.custom(jakartaRegularName, size: 12) + static let jakartaRegular6 = Font.custom(jakartaRegularName, size: 6) + static let jakartaRegular18 = Font.custom(jakartaRegularName, size: 18) + static let jakartaRegular40 = Font.custom(jakartaRegularName, size: 40) + static let jakartaBold12 = Font.custom(jakartaBold, size: 12) + static let jakartaBold14 = Font.custom(jakartaBold, size: 14) + static let jakartaBold16 = Font.custom(jakartaBold, size: 16) + static let jakartaBold18 = Font.custom(jakartaBold, size: 18) + static let jakartaBold22 = Font.custom(jakartaBold, size: 22) + static let jakartaBold24 = Font.custom(jakartaBold, size: 24) + static let jakartaBold64 = Font.custom(jakartaBold, size: 64) + static let jakartaBold40 = Font.custom(jakartaBold, size: 40) + static let interRegular14 = Font.custom(interRegular, size: 14) + static let interRegular16 = Font.custom(interRegular, size: 16) + static let interRegular12 = Font.custom(interRegular, size: 12) + static let interBold14 = Font.custom(interBold, size: 14) + static let redHatSemibold24 = Font.custom(redHatSemibold, size: 24) + static let redHatSemibold18 = Font.custom(redHatSemibold, size: 18) + static let redHat24 = Font.custom(redHat, size: 24) + static let redHat14 = Font.custom(redHat, size: 14) + static let redHat12 = Font.custom(redHat, size: 12) + static let jakartaMedium14 = Font.custom(jakartaMedium, size: 14) + static let jakartaMedium18 = Font.custom(jakartaMedium, size: 18) + static let jakartaMedium40 = Font.custom(jakartaMedium, size: 40) + +} diff --git a/medmap/Network/FluxReaderAPI.swift b/medmap/Network/MedMapAPI.swift similarity index 100% rename from medmap/Network/FluxReaderAPI.swift rename to medmap/Network/MedMapAPI.swift diff --git a/medmap/ViewModels/DashboardViewModel.swift b/medmap/ViewModels/DashboardViewModel.swift new file mode 100644 index 0000000..2044b25 --- /dev/null +++ b/medmap/ViewModels/DashboardViewModel.swift @@ -0,0 +1,7 @@ +// +// DashboardViewModel.swift +// medmap +// +// Created by RSA on 28/08/2026. +// + diff --git a/medmap/ViewModels/ViewModel.swift b/medmap/ViewModels/ViewModel.swift index 7fc86c9..2a4006b 100644 --- a/medmap/ViewModels/ViewModel.swift +++ b/medmap/ViewModels/ViewModel.swift @@ -14,7 +14,7 @@ import UserNotifications class ViewModel: ObservableObject { - + var appStateManager: AppStateManager = DIContainer.shared.resolve() @Published var isLoading: Bool = false diff --git a/medmap/Views/DashboardView.swift b/medmap/Views/DashboardView.swift new file mode 100644 index 0000000..005b429 --- /dev/null +++ b/medmap/Views/DashboardView.swift @@ -0,0 +1,7 @@ +// +// DashboardView.swift +// medmap +// +// Created by RSA on 28/08/2026. +// + diff --git a/medmap/Views/SplashScreenView.swift b/medmap/Views/SplashScreenView.swift index d7ba5bf..cbce4ed 100644 --- a/medmap/Views/SplashScreenView.swift +++ b/medmap/Views/SplashScreenView.swift @@ -12,158 +12,39 @@ struct SplashScreenView: View { @ObservedObject var viewModel: SplashScreenViewModel - @State private var glowPulse = false - @State private var logoScale = 0.85 - @State private var logoOpacity = 0.0 - @State private var ringRotate = 0.0 - @State private var ringOpacity = 0.0 - var body: some View { ZStack { - - // ── Deep black base ────────────────────────────────────────── - Color.black.ignoresSafeArea() - - // ── Bottom-half warm particle field ────────────────────────── - SplashParticleField() - .ignoresSafeArea() - .opacity(0.90) - - // ── Radial glow anchored to centre top ─────────────────────── + + + Color.appBackground.ignoresSafeArea() + VStack(spacing: 0) { - RadialGradient( - colors: [ - Color.orange.opacity(glowPulse ? 0.22 : 0.10), - Color.yellow.opacity(glowPulse ? 0.10 : 0.04), - Color.clear - ], - center: .center, - startRadius: 0, - endRadius: 340 - ) - .frame(width: 680, height: 680) - .blur(radius: 48) - .scaleEffect(glowPulse ? 1.12 : 1.0) - .animation( - .easeInOut(duration: 3.8).repeatForever(autoreverses: true), - value: glowPulse - ) + + HStack { + Image("splashLogo") + .frame(width: 150, height: 180) + .padding(.leading, 70) + .padding(.top, 90) + Spacer() + } + Spacer() + + Image("splashImage") + .padding(.top, 20) + Spacer() + + Image("corporateLogos") + .padding(.bottom, 35) + } .ignoresSafeArea() - - // ── Orbiting arc ring around logo ──────────────────────────── - ZStack { - // slow outer arc - Circle() - .trim(from: 0.0, to: 0.28) - .stroke( - LinearGradient( - colors: [Color.orange.opacity(0.45), Color.clear], - startPoint: .leading, endPoint: .trailing - ), - style: StrokeStyle(lineWidth: 1.0, lineCap: .round) - ) - .frame(width: 210, height: 210) - .rotationEffect(.degrees(ringRotate)) - .opacity(ringOpacity) - - // faster inner arc - Circle() - .trim(from: 0.55, to: 0.72) - .stroke( - LinearGradient( - colors: [Color.yellow.opacity(0.30), Color.clear], - startPoint: .leading, endPoint: .trailing - ), - style: StrokeStyle(lineWidth: 0.6, lineCap: .round) - ) - .frame(width: 175, height: 175) - .rotationEffect(.degrees(-ringRotate * 1.4)) - .opacity(ringOpacity) - } - - // ── Logo centred ───────────────────────────────────────────── - VStack(spacing: 0) { - Spacer() - - - Spacer() - } + } .ignoresSafeArea() .onAppear { - // Logo entrance - withAnimation(.spring(response: 0.75, dampingFraction: 0.72).delay(0.1)) { - logoScale = 1.0 - logoOpacity = 1.0 - } - // Glow breath - glowPulse = true - - // Ring fade-in then continuous rotation - withAnimation(.easeIn(duration: 0.8).delay(0.4)) { - ringOpacity = 1.0 - } - withAnimation(.linear(duration: 12).repeatForever(autoreverses: false).delay(0.4)) { - ringRotate = 360 - } - + } } } - -// MARK: - Splash Particle Field -// Warm, slow-drifting particles — denser than the reader version, tinted orange/gold - -struct SplashParticleField: View { - - @State private var tick: Double = 0 - private let timer = Timer.publish(every: 1.0 / 60.0, on: .main, in: .common).autoconnect() - - private struct Particle { - let x, y, speed, phase: Double - let radius: CGFloat - let opacity: Double - let warm: Bool // warm (orange/gold) vs cool (white) - } - - private let particles: [Particle] = (0..<120).map { i in - let seed = Double(i) * 1.618033 - return Particle( - x: (seed * 0.7369).truncatingRemainder(dividingBy: 1.0), - y: (seed * 1.2345).truncatingRemainder(dividingBy: 1.0), - speed: Double.random(in: 8...28), - phase: Double.random(in: 0...Double.pi * 2), - radius: CGFloat(Double.random(in: 0.7...2.8)), - opacity: Double.random(in: 0.04...0.28), - warm: i % 3 != 0 - ) - } - - var body: some View { - Canvas { ctx, size in - let t = tick - for p in particles { - let x = (p.x + t / p.speed).truncatingRemainder(dividingBy: 1.0) * size.width - let drift = sin(t / (p.speed * 0.6) + p.phase) * 22 - let y = p.y * size.height + CGFloat(drift) - let pulse = 0.65 + 0.35 * sin(t / 2.5 + p.phase) - let alpha = p.opacity * pulse - let color: Color = p.warm - ? Color(red: 1.0, green: 0.62, blue: 0.18).opacity(alpha) - : Color.white.opacity(alpha * 0.55) - - ctx.fill( - Path(ellipseIn: CGRect( - x: x - p.radius, y: y - p.radius, - width: p.radius * 2, height: p.radius * 2 - )), - with: .color(color) - ) - } - } - .onReceive(timer) { _ in tick += 1.0 / 60.0 } - } -}