basics api client
This commit is contained in:
parent
8e08130776
commit
a3f4586ced
12
medmap/Assets.xcassets/corporateLogos.imageset/Contents.json
vendored
Normal file
12
medmap/Assets.xcassets/corporateLogos.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "corporateLogos.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
9
medmap/Assets.xcassets/corporateLogos.imageset/corporateLogos.svg
vendored
Normal file
9
medmap/Assets.xcassets/corporateLogos.imageset/corporateLogos.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 170 KiB |
12
medmap/Assets.xcassets/splashImage.imageset/Contents.json
vendored
Normal file
12
medmap/Assets.xcassets/splashImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "splashImage.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
medmap/Assets.xcassets/splashImage.imageset/splashImage.png
vendored
Normal file
BIN
medmap/Assets.xcassets/splashImage.imageset/splashImage.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 373 KiB |
12
medmap/Assets.xcassets/splashLogo.imageset/Contents.json
vendored
Normal file
12
medmap/Assets.xcassets/splashLogo.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "splashLogo.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
11
medmap/Assets.xcassets/splashLogo.imageset/splashLogo.svg
vendored
Normal file
11
medmap/Assets.xcassets/splashLogo.imageset/splashLogo.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 32 KiB |
63
medmap/Extensions/Color+Extensions.swift
Normal file
63
medmap/Extensions/Color+Extensions.swift
Normal file
@ -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")
|
||||
}
|
||||
86
medmap/Extensions/Data+Extensions.swift
Normal file
86
medmap/Extensions/Data+Extensions.swift
Normal file
@ -0,0 +1,86 @@
|
||||
//
|
||||
// Data+Extensions.swift
|
||||
// FluxRead
|
||||
//
|
||||
// Created by RSA on 15/02/2026.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Data {
|
||||
|
||||
init<T>(copyingBufferOf array: [T]) {
|
||||
self = array.withUnsafeBufferPointer(Data.init)
|
||||
}
|
||||
|
||||
func toArray<T>(type: T.Type) -> [T] where T: ExpressibleByIntegerLiteral {
|
||||
var array = [T](repeating: 0, count: self.count / MemoryLayout<T>.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)
|
||||
}
|
||||
}
|
||||
22
medmap/Extensions/Date+extensions.swift
Normal file
22
medmap/Extensions/Date+extensions.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
49
medmap/Extensions/Font+CustomFonts.swift
Executable file
49
medmap/Extensions/Font+CustomFonts.swift
Executable file
@ -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)
|
||||
|
||||
}
|
||||
7
medmap/ViewModels/DashboardViewModel.swift
Normal file
7
medmap/ViewModels/DashboardViewModel.swift
Normal file
@ -0,0 +1,7 @@
|
||||
//
|
||||
// DashboardViewModel.swift
|
||||
// medmap
|
||||
//
|
||||
// Created by RSA on 28/08/2026.
|
||||
//
|
||||
|
||||
@ -14,7 +14,7 @@ import UserNotifications
|
||||
|
||||
class ViewModel: ObservableObject {
|
||||
|
||||
|
||||
var appStateManager: AppStateManager = DIContainer.shared.resolve()
|
||||
|
||||
@Published var isLoading: Bool = false
|
||||
|
||||
|
||||
7
medmap/Views/DashboardView.swift
Normal file
7
medmap/Views/DashboardView.swift
Normal file
@ -0,0 +1,7 @@
|
||||
//
|
||||
// DashboardView.swift
|
||||
// medmap
|
||||
//
|
||||
// Created by RSA on 28/08/2026.
|
||||
//
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user