foxygit / FORTIS Log in
commits tags
README.md

FORTIS — Tränings- och hälsoapp

Personlig träningsapp byggd i React Native + Expo. Spårning av träning, kost, vikt och progress med 5 teman och offline-stöd.


Innehåll


Kom igång

cd fortis
npx expo start
  • iOS Simulator: tryck i i terminalen
  • Android Emulator: tryck a i terminalen
  • Fysisk enhet: skanna QR-koden med Expo Go

All data sparas lokalt med AsyncStorage — ingen server krävs.


Funktioner

Implementerat i React Native-appen

FunktionStatusDetaljer
Splash screenAnimerad SVG-ring + progress-bar, 2.8 sek → fade
Onboarding5-stegs wizard, Mifflin-St Jeor TDEE, takt-val, kan hoppas över
Route guardRedirect till onboarding om ingen profil, tillbaka vid profil
DashboardSVG-kaloriering, makrobars, vattendroppar, dagens pass, streak
TräningsschemaVeckostrip mån–sön, pass per dag, starta pass
Aktiv träningSets/reps/vikt, vilotimer per övning, passtimer, spara till logg
Övningsbibliotek75 övningar, utrustningschips, filtermodal (muskelgrupp + svårighet), sök
Egna övningarSkapa/ta bort egna övningar, EGET-badge, persist i fortis_custom_exercises
Kostlogg30 livsmedel, sök per måltid, gram-input, live makrobars
Egna livsmedelSkapa/ta bort egna livsmedel, EGET-badge, persist i fortis_custom_foods
MätningarLogga vikt, midja, fettprocent — persist i fortis_measurements
ViktgrafSVG-linjegraf senaste 7 mätningarna med y-axel + datumrubriker
Profil-redigeringRedigera vikt, mål, takt, aktivitetsnivå — live makropreview
Achievements10 achievements, grid i profil med låsta/upplåsta
Tema-system5 teman, väljs i Profil, persist i fortis_theme
5 temanFortis, Dark, Midnight, Forest, Stone

Ej implementerat ännu

FunktionFasBlockerat av
Barcodeläsare4Expo Camera
Open Food Facts API4Native network + barcode
Push-notifikationer4Expo Notifications
Progressionsgraf per övningTräningslogg saknar aggregering per övning i RN
Export av data (JSON)Inte prioriterat ännu

Skärmar

🏠 Hem (Dashboard)

Daglig översikt på ett ögonkast:

  • Kaloriering — SVG-cirkel med kcal loggat / mål
  • Makrobars — Protein / Kolhydrater / Fett med procentfyllning
  • Vattenspårning — visuella droppar, +250 ml snabbknapp, dagsmål 2 500 ml
  • Dagens pass — namn + antal övningar, länk till aktiv träning
  • Streak-pill — antal dagar i rad med loggat pass

📅 Schema

  • Veckostrip måndag–söndag, aktiv dag markerad
  • Pass per dag med övningar listade
  • Starta pass direkt → aktiv träningsvy

Aktiv träning

  • Varje övning med sets × reps × vikt, editerbara live
  • ✓-knapp per set → startar vilotimer (tid från övningens restSec)
  • Passtimer (MM:SS) i headern
  • Avsluta → sparar till träningslogg

📚 Övningsbibliotek

Filtreringssystem — hybrid-UX:

  • Nivå 1: Horisontellt scrollbara utrustningschips (alltid synliga)
  • Nivå 2: Filtermodal — muskelgrupp + svårighet, badge visar antal aktiva
  • Fritextsök i realtid

Detaljvy: steg-för-steg instruktioner, sekundära muskler
Egna övningar: "+ Ny övning"-FAB, EGET-badge, 🗑 ta bort i detaljvy

🍎 Kost

  • Summarykortet — kcal loggat / mål, live makroboxar
  • Måltidssektioner: Frukost / Lunch / Middag / Mellanmål
  • "+ Lägg till" per måltid → sök-modal med alla livsmedel
  • Egna livsmedel — "Skapa eget livsmedel" i sök-modalen, formulär för namn + makron per 100g
  • EGET-badge + 🗑-knapp på egna i listan
  • ✕ på loggad post tar bort direkt

👤 Profil

  • Statistik — pass totalt · streak · kcal/dag
  • Progress — "Logga"-knapp öppnar mätningsmodal (vikt, midja, fettprocent)
    • SVG-linjegraf över senaste 7 viktmätningarna
    • Lista på 4 senaste mätningarna
  • Profil-data — "Redigera"-knapp: vikt, målvikt, mål, takt, aktivitetsnivå + live makropreview
  • Tema-väljare — 5 färgswatches
  • Achievements — 10 achievements, låsta i 40% opacity
  • Återställ all data

Övningsbibliotek

75 övningar från wger.de + egna.

Fördelning per muskelgrupp

MuskelgruppAntal
Ben18
Rygg11
Core11
Bröst10
Armar9
Axlar8
Kondition7
Helkropp1

Övningsstruktur

{
  id: 'exercise_id',
  name: 'Exercise Name',
  muscle: 'Ben',            // primär muskelgrupp
  secondary: 'Rumpa, Core', // sekundära (sträng)
  equip: 'Kroppsvikt',      // måste matcha EQUIP_CHIPS
  icon: '🦵',
  diff: 'Nybörjare',        // Nybörjare | Medel | Avancerad
  type: 'Styrka',           // Styrka | Kondition | Rörlighet
  isTimer: false,           // true = tidsbaserad (plank, löpband etc.)
  instructions: ['Steg 1', 'Steg 2', ...]
}

wger.de API för fler övningar:

GET https://wger.de/api/v2/exerciseinfo/{id}/?format=json

Kostdatabas

30 vanliga livsmedel, alla med makrovärden per 100g. Egna livsmedel läggs till direkt i appen.

KategoriLivsmedel
Kött & fiskKycklingfilé, Nötfärs, Lax, Tonfisk, Ägg, Räkor
MejeriMjölk, Grekisk yoghurt, Keso, Ost, Kvarg
SpannmålHavregryn, Ris, Pasta, Vitt bröd, Rågbröd, Tortilla
GrönsakerBroccoli, Spenat, Potatis, Sötpotatis
FruktBanan, Äpple, Blåbär
Nötter & övrigtMandlar, Jordnötssmör, Olivolja, Avokado, Proteinpulver, Kidneybönor

Eget livsmedel-schema:

{ id: 'cf123', name: 'Hemmagjord granola', cal: 420, prot: 9, carbs: 58, fat: 16, isCustom: true }

Tema-system

5 inbyggda teman, väljs i Profil-fliken.

TemaBakgrundAccentKaraktär
FortisVarm off-white #F2F1EDOrange #E85D26Standard, varm
DarkKol #121212Orange #E85D26Mörkt läge
MidnightNavy #0D1117Blå #5B8BFFGitHub-mörkt
ForestNaturgrön #F0F4F0Grön #2D9E5FLugnt, naturligt
StoneCool grå #F5F5F4Indigo #6366F1Modernt, minimalt

Tema-systemet är frikopplat:

src/constants/theme.ts     ← THEMES-objekt + Theme-interface
src/store/useThemeStore.ts ← Zustand: themeId, setTheme(), persist
src/hooks/useTheme.ts      ← hook: returnerar aktiva temavärden

Inga färgvärden hårdkodas i komponenter. Nya teman läggs enbart till i theme.ts.


Achievements

10 achievements som låses upp baserat på träningshistorik.

AchievementIkonVillkor
Första steget🏆Slutför 1 pass
På rulle🔥3 dagars streak
Veckovinnare🔥🔥7 dagars streak
Hängiven💪5 pass totalt
Maskin10 pass totalt
Elit👑20 pass totalt
Morgonfågel🌅Tränat innan kl 08:00
Maraton🏅60+ min i ett pass
Helkropp🎯Tränat 5+ muskelgrupper
Personbästa📈Sätt ett personligt rekord

Datalagring

Storage-nycklar

NyckelTypInnehåll
fortis_profileProfileProfildata inkl. makromål
fortis_workoutsWorkoutSchedule[]Schemalagda pass
fortis_workoutlogWorkoutLogEntry[]Genomförda pass
fortis_foodlogFoodLogEntry[]Loggad mat per dag
fortis_water_YYYY-MM-DDnumberVattenintag i ml
fortis_measurementsMeasurementEntry[]Vikt, midja, fettprocent
fortis_custom_foodsFoodItem[]Egna livsmedel
fortis_custom_exercisesExercise[]Egna övningar
fortis_themestringAktivt tema-ID

Datamodeller

interface Profile {
  id: string
  name: string
  gender: 'male' | 'female' | 'other'
  age: number
  heightCm: number
  weightKg: number
  goalWeightKg: number
  goal: 'lose' | 'maintain' | 'gain'
  pace: 'slow' | 'moderate' | 'fast'
  activityLevel: 'sedentary' | 'light' | 'moderate' | 'very_active'
  macroGoals: MacroGoals
  createdAt: string
  updatedAt: string
}

interface MacroGoals {
  calories: number
  proteinG: number
  carbsG: number
  fatG: number
}

interface Exercise {
  id: string
  name: string
  muscle: string
  secondary?: string
  equip: string
  icon: string
  diff: 'Nybörjare' | 'Medel' | 'Avancerad'
  type: 'Styrka' | 'Kondition' | 'Rörlighet'
  isTimer?: boolean
  instructions: string[]
  wgerId?: number
  isCustom?: boolean
}

interface WorkoutSchedule {
  id: string
  name: string
  day: number         // 0=mån … 6=sön
  exercises: ScheduledExercise[]
}

interface ScheduledExercise {
  exerciseId: string
  sets: number
  reps?: number
  durationSec?: number
  weight?: number
  restSec: number
}

interface WorkoutLogEntry {
  id: string
  workoutId: string
  date: string
  startedAt: string
  endedAt?: string
  durationMin: number
  exercises: LoggedSet[][]
  completedManually: boolean
}

interface LoggedSet {
  reps?: number
  weight?: number
  durationSec?: number
  done: boolean
}

interface FoodItem {
  id: string
  name: string
  cal: number
  prot: number
  carbs: number
  fat: number
  isCustom?: boolean
}

interface FoodLogEntry {
  id: string
  date: string
  meal: 'breakfast' | 'lunch' | 'dinner' | 'snack'
  foodId: string
  foodName: string
  amountG: number
  cal: number
  prot: number
  carbs: number
  fat: number
}

interface MeasurementEntry {
  id: string
  date: string
  weightKg?: number
  waistCm?: number
  bodyFatPct?: number
  notes?: string
}

interface WaterLogEntry {
  id: string
  date: string
  totalMl: number
}

Arkitektur

fortis/
├── app/
│   ├── _layout.tsx              # Root layout, store-laddning, route guard
│   ├── (tabs)/
│   │   ├── _layout.tsx          # Tab bar med 5 flikar (Lucide-ikoner)
│   │   ├── index.tsx            # Dashboard
│   │   ├── schedule.tsx         # Träningsschema
│   │   ├── library.tsx          # Övningsbibliotek
│   │   ├── nutrition.tsx        # Kostlogg
│   │   └── profile.tsx          # Profil & progress
│   ├── onboarding/
│   │   └── index.tsx            # Splash-animation + 5-stegs wizard
│   └── workout/
│       └── [id].tsx             # Aktiv träningsvy (fullscreen modal)
│
└── src/
    ├── constants/
    │   ├── types.ts             # Alla TypeScript-gränssnitt
    │   ├── exercises.ts         # 75 övningar
    │   ├── foods.ts             # 30 livsmedel
    │   └── theme.ts             # 5 teman + Theme-interface
    ├── store/
    │   ├── useProfileStore.ts   # Zustand: profile, macroGoals, hasLoaded
    │   ├── useWorkoutStore.ts   # Zustand: workouts, workoutLog, calcStreak()
    │   ├── useNutritionStore.ts # Zustand: foodLog, waterToday, customFoods
    │   ├── useMeasurementsStore.ts # Zustand: measurements
    │   └── useThemeStore.ts     # Zustand: themeId, persist
    ├── hooks/
    │   └── useTheme.ts          # Returnerar aktiva temavärden
    └── lib/
        ├── storage.ts           # AsyncStorage wrapper + STORAGE_KEYS
        └── macroCalc.ts         # calcTDEE(), calcMacros(), PACE_LABELS

Roadmap

FasInnehållStatus
Fas 1Splash, Onboarding, Dashboard, Schema, Aktiv träning, Bibliotek✅ Klar
Fas 2Progressionsgraf, Achievements/streaks, Utbyggt bibliotek (75 övningar)✅ Klar
Fas 3Kostlogg, Makrospårning, Matdatabas, Vattenlogg, Egna livsmedel✅ Klar
Fas 4Profil-redigering, Viktgraf, Mätningar, Egna livsmedel✅ Klar
Fas 5React Native + Expo migration, AsyncStorage, offline-stöd✅ Klar
Fas 6Barcodeläsare, Open Food Facts API, Push-notifikationer🔜

Närmast kvar

  • [ ] Barcodeläsare (Expo Camera) — ersätter sökning med skanning
  • [ ] Open Food Facts API-integration
  • [ ] Push-notifikationer (pass-påminnelse, vattenintag, invägningsdag)
  • [ ] Progressionsgraf per övning i RN
  • [ ] Export av all data till JSON

Tech stack

KomponentVal
FrameworkReact Native + Expo SDK 54
RoutingExpo Router v6 (file-based)
StateZustand
Persistens@react-native-async-storage/async-storage
Chartsreact-native-svg (hand-byggda SVG-grafer)
AnimationerReact Native Animated API
Iconslucide-react-native
Barcode— (ej implementerat, kräver Expo Camera)
Notiser— (ej implementerat, kräver Expo Notifications)

Kaloriformel

TDEE beräknas med Mifflin-St Jeor:

Män:     BMR = 10×vikt + 6.25×längd − 5×ålder + 5
Kvinnor: BMR = 10×vikt + 6.25×längd − 5×ålder − 161

TDEE = BMR × aktivitetsmultiplikator
  sedentary:   × 1.2
  light:       × 1.375
  moderate:    × 1.55
  very_active: × 1.725

Takt-justering:
  lose:
    slow:     TDEE − 250 kcal  (~0,25 kg/vecka)
    moderate: TDEE − 500 kcal  (~0,5 kg/vecka)
    fast:     TDEE − 750 kcal  (~0,75 kg/vecka)
  gain:
    slow:     TDEE + 200 kcal  (lean bulk, ~0,2 kg/vecka)
    moderate: TDEE + 350 kcal  (~0,35 kg/vecka)
    fast:     TDEE + 500 kcal  (~0,5 kg/vecka)
  maintain:   TDEE (ingen justering)

Minimum: 1 200 kcal/dag

Senast uppdaterad: 2026-05-01