From d260f0e4ece528fac316b2b19fc1b01c0e65a52e Mon Sep 17 00:00:00 2001 From: Stefan Poindl <55439476+thwbh@users.noreply.github.com> Date: Sun, 15 Feb 2026 23:42:27 +0100 Subject: [PATCH] branding update (#202) * intake and weight 'find_last' functions should sort by added date, not id * add settings menu backdrop; add icons to settings menu * redesign wizard, welcome and setup page * redesign settings routes * mark settings icon as active in dock when settings subpages are hit * decrease FAB z-index to avoid settings menu overlap * fix android safezones * fix FAB alignment * tidy up WeightGoalsCard * make date diff calculations zero-based and pad display value in frontend * fix FAB float * fix husky hook staging uncommitted rs files * update changelog --- .husky/pre-commit | 12 +- CHANGELOG.md | 44 +++++ src-tauri/src/service/dashboard.rs | 25 ++- src-tauri/src/service/intake.rs | 2 +- src-tauri/src/service/progress.rs | 22 ++- src-tauri/src/service/weight.rs | 2 +- src-tauri/tests/cmd/test_dashboard_cmd.rs | 9 +- src/app.html | 2 +- .../component/journey/WeightGoalsCard.svelte | 44 +++-- .../component/journey/WeightGoalsCard.test.ts | 63 ++----- src/lib/component/settings/Settings.svelte | 52 +++--- src/lib/component/wizard/Finish.svelte | 2 +- src/lib/component/wizard/Setup.svelte | 73 ++++---- src/lib/component/wizard/body/Report.svelte | 128 ++++++-------- src/routes/(app)/+layout.svelte | 10 +- src/routes/(app)/+page.svelte | 23 ++- src/routes/(app)/about/+page.svelte | 22 +-- src/routes/(app)/export/+page.svelte | 128 ++++++++------ src/routes/(app)/import/+page.svelte | 152 +++++++++------- src/routes/(app)/profile/+page.svelte | 44 ++--- src/routes/(app)/progress/+page.svelte | 163 ++++++++++-------- src/routes/(app)/wizard/+page.svelte | 120 ++++++++++--- src/routes/setup/+page.svelte | 103 ++++++++++- src/routes/welcome/+page.svelte | 2 +- src/style.css | 8 + 25 files changed, 747 insertions(+), 508 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index f5b8dde0..bc90188f 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,11 +1,9 @@ -# Format Rust code in src-tauri -echo "πŸ¦€ Formatting Rust code..." -cd src-tauri && cargo fmt && cd .. - -# Stage all formatted Rust files -RUST_FILES=$(git diff --name-only --diff-filter=ACM "*.rs" | grep "^src-tauri/" || true) +# Format and re-stage any staged Rust files +RUST_FILES=$(git diff --cached --name-only --diff-filter=ACM "*.rs" | grep "^src-tauri/" || true) if [ -n "$RUST_FILES" ]; then - echo "πŸ“¦ Staging formatted Rust files..." + echo "πŸ¦€ Formatting Rust code..." + cd src-tauri && cargo fmt && cd .. + echo "πŸ“¦ Re-staging formatted Rust files..." git add $RUST_FILES fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 3afbe3e5..123720e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,47 @@ All notable changes to LibreFit will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project uses [Calendar Versioning](https://calver.org/) (YY.WW.MICRO format). +## [26.07.2] - 2026-02-15 + +### Changed + +- Branded headers on setup wizard, profile, import, and export pages +- Redesigned wizard step indicator with animated progress bar +- Weight goals card now shows an inline start-to-target flow with trend indicator +- Styled list indicators on import and export pages +- Settings menu with icons and improved close behavior +- Progress page shows a message when fewer than 2 days of data are tracked + +### Fixed + +- Progress charts no longer include today's incomplete data +- Day counter displays correctly on dashboard and progress pages +- Floating action button stays visible when scrolling +- Android safe area insets for devices with notches or gesture navigation + +## [26.07.1] - 2026-02-12 + +### Changed + +- Redesigned dashboard with branded header, journey progress bar, and calorie plan overview +- Redesigned progress page with weight and intake charts, category breakdown, and trend indicators +- Redesigned history page with category overview and improved entry management +- New calorie plan card with target and average intake progress bars +- Updated activity level and meal category colors +- Redesigned category picker +- Weight tracker entries can now be added and edited from the history page + +### Removed + +- Standalone review page (merged into dashboard) + +## [26.07] - 2026-02-10 + +### Changed + +- New app logo and refreshed color palette +- Updated app icons across all platforms + ## [25.49.1] - 2025-12-03 ### Fixed @@ -44,5 +85,8 @@ and this project uses [Calendar Versioning](https://calver.org/) (YY.WW.MICRO fo - F-Droid compatible build process - Reproducible builds support +[26.07.2]: https://github.com/thwbh/librefit/releases/tag/26.07.2 +[26.07.1]: https://github.com/thwbh/librefit/releases/tag/26.07.1 +[26.07]: https://github.com/thwbh/librefit/releases/tag/26.07 [25.49.1]: https://github.com/thwbh/librefit/releases/tag/25.49.1 [25.49.0]: https://github.com/thwbh/librefit/releases/tag/25.49.0 diff --git a/src-tauri/src/service/dashboard.rs b/src-tauri/src/service/dashboard.rs index f6065a2a..73603794 100644 --- a/src-tauri/src/service/dashboard.rs +++ b/src-tauri/src/service/dashboard.rs @@ -2,7 +2,7 @@ use crate::db::connection::DbPool; use crate::service::intake::{FoodCategory, Intake, IntakeTarget}; use crate::service::user::LibreUser; use crate::service::weight::{WeightTarget, WeightTracker}; -use chrono::{Days, NaiveDate, TimeDelta}; +use chrono::{NaiveDate, TimeDelta}; use diesel::SqliteConnection; use serde::{Deserialize, Serialize}; use std::ops::Sub; @@ -52,14 +52,6 @@ impl Dashboard { let intake_target_end_date = NaiveDate::parse_from_str(&intake_target.end_date, "%Y-%m-%d") .map_err(|_| "Invalid date format".to_string())?; - // Calculate end_date for current_day calculation - let today_date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d").unwrap(); - let end_date: NaiveDate = if today_date < intake_target_end_date { - today_date.checked_sub_days(Days::new(1)).unwrap() - } else { - intake_target_end_date - }; - // Calculate date ranges let week_start_str = get_date_range_begin(&date, chrono::Duration::days(7)); let month_start_str = get_date_range_begin(&date, chrono::Duration::weeks(4)); @@ -84,11 +76,16 @@ impl Dashboard { // Fetch food categories let food_categories = FoodCategory::all(conn).unwrap_or_else(|_| vec![]); - // Calculate current day - let day_count: i32 = end_date - .signed_duration_since(intake_target_start_date) - .num_days() as i32; - let current_day: i32 = day_count + 1; // Day count will be zero at the first day + let current_day: i32 = if date < intake_target_end_date { + // Elapsed days (0 on first day) + date.signed_duration_since(intake_target_start_date) + .num_days() as i32 + } else { + // Plan complete: cap at total + intake_target_end_date + .signed_duration_since(intake_target_start_date) + .num_days() as i32 + }; let days_total: i32 = intake_target_end_date .signed_duration_since(intake_target_start_date) diff --git a/src-tauri/src/service/intake.rs b/src-tauri/src/service/intake.rs index ef53d201..5ed5cd98 100644 --- a/src-tauri/src/service/intake.rs +++ b/src-tauri/src/service/intake.rs @@ -315,7 +315,7 @@ impl IntakeTarget { /// Find the last intake target pub fn find_last(conn: &mut SqliteConnection) -> QueryResult { intake_target::table - .order(intake_target::id.desc()) + .order(intake_target::added.desc()) .first::(conn) } } diff --git a/src-tauri/src/service/progress.rs b/src-tauri/src/service/progress.rs index fec5a813..a2f31975 100644 --- a/src-tauri/src/service/progress.rs +++ b/src-tauri/src/service/progress.rs @@ -67,7 +67,8 @@ impl Progress { let today_date: NaiveDate = NaiveDate::parse_from_str(date_str, "%Y-%m-%d") .map_err(|_| "Invalid date format".to_string())?; - let end_date: NaiveDate = if today_date < intake_target_end_date { + // Query up to yesterday to avoid incomplete data skewing charts + let query_end_date: NaiveDate = if today_date <= intake_target_end_date { today_date .checked_sub_days(Days::new(1)) .ok_or("Failed to subtract day".to_string())? @@ -75,22 +76,29 @@ impl Progress { intake_target_end_date }; - let end_date_str = end_date.format("%Y-%m-%d").to_string(); + let query_end_str = query_end_date.format("%Y-%m-%d").to_string(); let intake: Vec = - Intake::find_by_date_range(conn, &intake_target.start_date, &end_date_str) + Intake::find_by_date_range(conn, &intake_target.start_date, &query_end_str) .unwrap_or_default(); let weight_tracker: Vec = - WeightTracker::find_by_date_range(conn, &weight_target.start_date, &end_date_str) + WeightTracker::find_by_date_range(conn, &weight_target.start_date, &query_end_str) .unwrap_or_default(); let intake_chart_data = process_intake(conn, &intake)?; let weight_chart_data = process_weight(&weight_tracker); - let days_passed: i32 = end_date - .signed_duration_since(intake_target_start_date) - .num_days() as i32; + // Elapsed days (0-indexed: 0 on first day), capped at days_total after plan ends + let days_passed: i32 = if today_date < intake_target_end_date { + today_date + .signed_duration_since(intake_target_start_date) + .num_days() as i32 + } else { + intake_target_end_date + .signed_duration_since(intake_target_start_date) + .num_days() as i32 + }; let days_total: i32 = intake_target_end_date .signed_duration_since(intake_target_start_date) diff --git a/src-tauri/src/service/weight.rs b/src-tauri/src/service/weight.rs index a0e30aa3..7366c557 100644 --- a/src-tauri/src/service/weight.rs +++ b/src-tauri/src/service/weight.rs @@ -253,7 +253,7 @@ impl WeightTracker { /// Find most recent weight tracker entry pub fn get_latest(conn: &mut SqliteConnection) -> QueryResult { weight_tracker::table - .order(weight_tracker::id.desc()) + .order(weight_tracker::added.desc()) .first::(conn) } } diff --git a/src-tauri/tests/cmd/test_dashboard_cmd.rs b/src-tauri/tests/cmd/test_dashboard_cmd.rs index c701227b..55c95351 100644 --- a/src-tauri/tests/cmd/test_dashboard_cmd.rs +++ b/src-tauri/tests/cmd/test_dashboard_cmd.rs @@ -95,8 +95,8 @@ fn test_daily_dashboard_last_day_of_target() { assert!(result.is_ok()); let dashboard = result.unwrap(); - // Last day should be day 31 (when querying on the last day, it shows 31) - assert_eq!(dashboard.current_day, 31); + // Last day: 0-indexed elapsed days capped at days_total (30 for a Jan 1–Jan 31 range) + assert_eq!(dashboard.current_day, dashboard.days_total); } #[test] @@ -124,9 +124,8 @@ fn test_daily_dashboard_date_beyond_target() { assert!(result.is_ok()); let dashboard = result.unwrap(); - // When querying beyond target, it should still return successfully - // The current_day should be based on the end date, not the query date - assert_eq!(dashboard.current_day, 31); + // When querying beyond target, current_day caps at days_total + assert_eq!(dashboard.current_day, dashboard.days_total); } #[test] diff --git a/src/app.html b/src/app.html index e43e794b..bf77153b 100644 --- a/src/app.html +++ b/src/app.html @@ -3,7 +3,7 @@ - + %sveltekit.head% diff --git a/src/lib/component/journey/WeightGoalsCard.svelte b/src/lib/component/journey/WeightGoalsCard.svelte index 344c1918..d99e2465 100644 --- a/src/lib/component/journey/WeightGoalsCard.svelte +++ b/src/lib/component/journey/WeightGoalsCard.svelte @@ -1,22 +1,42 @@ -
-
- - +
+

Weight Goal

+
+
+ + kg + + Start +
+ +
+ {#if isGaining} + + {:else} + + {/if} + {Math.abs(weightDiff).toFixed(1)} kg +
+ +
+ + kg + + Target +
diff --git a/src/lib/component/journey/WeightGoalsCard.test.ts b/src/lib/component/journey/WeightGoalsCard.test.ts index d52eae72..b82d0249 100644 --- a/src/lib/component/journey/WeightGoalsCard.test.ts +++ b/src/lib/component/journey/WeightGoalsCard.test.ts @@ -7,76 +7,39 @@ import { setupVeilchenMock } from '../../../../tests/utils/mocks'; setupVeilchenMock(); describe('WeightGoalsCard Component', () => { - it('should render current weight', () => { + it('should render start and target labels', () => { render(WeightGoalsCard, { props: { - currentWeight: 75, - targetWeight: 70 + initialWeight: 85, + targetWeight: 80 } }); - expect(screen.getByText('Current Weight')).toBeInTheDocument(); + expect(screen.getByText('Start')).toBeInTheDocument(); + expect(screen.getByText('Target')).toBeInTheDocument(); }); - it('should render target weight', () => { - render(WeightGoalsCard, { - props: { - currentWeight: 80, - targetWeight: 75 - } - }); - - expect(screen.getByText('Target Weight')).toBeInTheDocument(); - }); - - it('should display weights as numbers', () => { + it('should render weight difference', () => { const { container } = render(WeightGoalsCard, { props: { - currentWeight: 85.5, - targetWeight: 80.0 + initialWeight: 85, + targetWeight: 80 } }); - // The component uses StatCard which renders the values + expect(screen.getByText('5.0 kg')).toBeInTheDocument(); expect(container).toBeDefined(); }); - it('should apply correct styling', () => { - const { container } = render(WeightGoalsCard, { - props: { - currentWeight: 90, - targetWeight: 85 - } - }); - - const wrapper = container.querySelector('.bg-base-100'); - expect(wrapper).toBeDefined(); - - const stats = container.querySelector('.stats'); - expect(stats).toBeDefined(); - }); - it('should handle weight gain scenario', () => { - render(WeightGoalsCard, { + const { container } = render(WeightGoalsCard, { props: { - currentWeight: 60, + initialWeight: 60, targetWeight: 70 } }); - expect(screen.getByText('Current Weight')).toBeInTheDocument(); - expect(screen.getByText('Target Weight')).toBeInTheDocument(); - }); - - it('should handle weight loss scenario', () => { - render(WeightGoalsCard, { - props: { - currentWeight: 100, - targetWeight: 85 - } - }); - - expect(screen.getByText('Current Weight')).toBeInTheDocument(); - expect(screen.getByText('Target Weight')).toBeInTheDocument(); + expect(screen.getByText('10.0 kg')).toBeInTheDocument(); + expect(container).toBeDefined(); }); }); diff --git a/src/lib/component/settings/Settings.svelte b/src/lib/component/settings/Settings.svelte index 18a898e1..cd4d2228 100644 --- a/src/lib/component/settings/Settings.svelte +++ b/src/lib/component/settings/Settings.svelte @@ -1,34 +1,42 @@ {#if open} - - + + - +
+ +
{/if} diff --git a/src/lib/component/wizard/Finish.svelte b/src/lib/component/wizard/Finish.svelte index 99f211ff..274cc55e 100644 --- a/src/lib/component/wizard/Finish.svelte +++ b/src/lib/component/wizard/Finish.svelte @@ -23,7 +23,7 @@ /> diff --git a/src/lib/component/wizard/Setup.svelte b/src/lib/component/wizard/Setup.svelte index cdba32ea..45ba4939 100644 --- a/src/lib/component/wizard/Setup.svelte +++ b/src/lib/component/wizard/Setup.svelte @@ -39,24 +39,31 @@ interface Props { userData?: LibreUser; bodyData?: BodyData; + currentStep?: number; + recommendation?: string; } const CalculationGoal = CalculationGoalSchema.enum; const CalculationSex = CalculationSexSchema.enum; const WizardRecommendation = WizardRecommendationSchema.enum; - let props: Props = $props(); + let { + userData: userDataProp, + bodyData: bodyDataProp, + currentStep = $bindable(1), + recommendation = $bindable('') + }: Props = $props(); // Create reactive local state for userData with default let userData = $state( - props.userData || { + userDataProp || { id: 1, name: 'Arnie', avatar: '' } ); - let bodyData = props.bodyData || { + let bodyData = bodyDataProp || { id: 0, age: 30, sex: CalculationSex.MALE, @@ -64,8 +71,6 @@ height: 180 }; - let currentStep = $state(1); - let wizardInput: WizardInput = $state({ age: bodyData.age, sex: CalculationSexSchema.safeParse(bodyData.sex).data!, @@ -144,6 +149,7 @@ if (result) { wizardResult = result; + recommendation = result.recommendation; if (result.recommendation === WizardRecommendation.LOSE) { chosenOption.customDetails = result.targetWeightUpper; @@ -309,52 +315,20 @@ {#if !showCompletion} {#snippet step1()} -
-

Body Parameters

-

- Let's start with some basic information about you -

-
{/snippet} {#snippet step2()} -
-

Activity Level

-

- How active are you during your day? Choose what describes your daily activity level best. -

-
{/snippet} {#snippet step3()} -
-

Your Results

-

- Here's what your body composition and metabolism look like -

-
{#if wizardResult} {/if} {/snippet} {#snippet step4()} -
- {#if wizardResult?.recommendation === WizardRecommendation.HOLD} -

Select Your Target Weight

- {:else} -

Choose Your Pace

- {/if} -

- {#if wizardResult?.recommendation === WizardRecommendation.HOLD} - Choose a target weight within your healthy weight range - {:else} - Select a calorie deficit that fits your lifestyle and goals - {/if} -

-
{#if wizardTargetWeightResult} {@const rates = Object.keys(wizardTargetWeightResult.dateByRate).map((v) => +v)} {@const targetDates = wizardTargetWeightResult.dateByRate} @@ -370,12 +344,6 @@ {/snippet} {#snippet step5()} -
-

Your Personalized Plan

-

- Here's your customized fitness journey roadmap -

-
{/snippet}
@@ -391,3 +359,22 @@ onRetry={handleRetry} /> {/if} + + diff --git a/src/lib/component/wizard/body/Report.svelte b/src/lib/component/wizard/body/Report.svelte index e270841d..a36aba22 100644 --- a/src/lib/component/wizard/body/Report.svelte +++ b/src/lib/component/wizard/body/Report.svelte @@ -5,7 +5,7 @@ type WizardInput, type WizardResult } from '$lib/api/gen'; - import { AlertBox, AlertType, AlertVariant, StatCard } from '@thwbh/veilchen'; + import { AlertBox, AlertType, AlertVariant } from '@thwbh/veilchen'; import { getBmiCategoryDisplayValue } from '$lib/enum'; import { z } from 'zod'; @@ -20,7 +20,6 @@ let { wizardResult, wizardInput }: Props = $props(); // Check if user is in low-normal BMI range (18.5-19.9) - // This is when they're in healthy range but have GAIN recommendation let isLowNormalBmi = $derived( wizardResult.bmi >= 18.5 && wizardResult.bmi < 20 && @@ -35,71 +34,72 @@ const getClassificationStyle = (category: string) => { if (classificationLose.safeParse(category).success) { - return 'badge-error'; + return 'bg-error text-content-error'; } else if (category === BmiCategory.Underweight) { - return 'badge-warning'; + return 'bg-warning text-content-warning'; } else { - return 'badge-success'; + return 'bg-success text-content-success'; } }; -
+
-
- - +
+
+ BMI + {wizardResult.bmi} + + {getBmiCategoryDisplayValue(wizardResult.bmiCategory)} + +
+
+ Recommendation + {wizardResult.recommendation.toLowerCase()} + + {wizardInput.weight} kg + +
- -
-

Your Metabolic Profile

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Age{wizardInput.age} years
Height{wizardInput.height} cm
Current Weight{wizardInput.weight} kg
Basal Metabolic Rate{wizardResult.bmr} kcal
Total Daily Energy Expenditure{wizardResult.tdee} kcal
+ +
+
+

+ Metabolic Profile +

+
+
+
+ Age + {wizardInput.age} years +
+
+ Height + {wizardInput.height} cm +
+
+ Weight + {wizardInput.weight} kg +
+
+ Basal Metabolic Rate + {wizardResult.bmr} kcal +
+
+ Daily Energy Expenditure + {wizardResult.tdee} kcal +
+
-
-

Your Analysis

+
+

Analysis

-
+

Your basal metabolic rate is {wizardResult.bmr} kcal. To maintain your @@ -108,7 +108,7 @@

{#if wizardResult.targetBmi} -
+

At {wizardInput.height}cm and {wizardInput.weight}kg, your BMI is @@ -120,7 +120,6 @@

{#if isLowNormalBmi} - You are currently in the healthy weight range

@@ -137,24 +136,11 @@

{:else if wizardResult.targetBmiLower <= wizardResult.bmi && wizardResult.bmi <= wizardResult.targetBmiUpper} - -
- + You are currently in the healthy weight range! -
+ {:else} Outside healthy range diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 24176909..518be4f5 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -26,7 +26,6 @@ id: 'home', label: 'Home', href: '/', - /* icon: House,*/ icon: JournalIcon, iconProps: { size: '1.25em', weight: 'bold' } }, @@ -34,21 +33,18 @@ id: 'progress', label: 'Progress', href: '/progress', - icon: ProgressIcon, - iconProps: { size: '1.25em', weight: 'bold' } + icon: ProgressIcon }, { id: 'history', label: 'History', href: '/history', - icon: HistoryIcon, - iconProps: { size: '1.25em', weight: 'bold' } + icon: HistoryIcon }, { id: 'settings', label: 'Settings', icon: SettingsIcon, - iconProps: { size: '1.25em', weight: 'bold' }, onclick: () => { isSettingsOpen = !isSettingsOpen; } @@ -63,7 +59,7 @@ item.href && (page.url.pathname === item.href || (page.url.pathname.startsWith(item.href) && item.href !== '/')) - )?.id + )?.id ?? 'settings' ); diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index 1666e54b..13057ae5 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -165,15 +165,22 @@ useRefresh(() => invalidate('data:dashboardData')); - /** Moves an element to document.body so `position: fixed` works inside transformed ancestors. */ - function portal(node: HTMLElement) { + const cubicOut = 'cubic-bezier(0.33, 1, 0.68, 1)'; + + // FAB transitioning effect + const portal = (node: HTMLElement) => { document.body.appendChild(node); + node.style.opacity = '0'; + node.style.transition = `opacity 150ms ${cubicOut}`; + setTimeout(() => (node.style.opacity = '1'), 100); return { destroy() { - node.remove(); + node.style.transition = `opacity 100ms ${cubicOut}`; + node.style.opacity = '0'; + setTimeout(() => node.remove(), 100); } }; - } + };
@@ -183,9 +190,7 @@
- {#if data.dashboardData.currentDay > 0} - Day {data.dashboardData.currentDay} - {/if} + Day {dashboard.currentDay + 1} {displayDate}
@@ -309,9 +314,9 @@
diff --git a/src/routes/(app)/about/+page.svelte b/src/routes/(app)/about/+page.svelte index c43cc348..ae15ce0b 100644 --- a/src/routes/(app)/about/+page.svelte +++ b/src/routes/(app)/about/+page.svelte @@ -1,40 +1,22 @@ About LibreFit -
- - +
{#if mounted} diff --git a/src/routes/(app)/export/+page.svelte b/src/routes/(app)/export/+page.svelte index 9ec7ed1c..1aebf7fd 100644 --- a/src/routes/(app)/export/+page.svelte +++ b/src/routes/(app)/export/+page.svelte @@ -1,7 +1,6 @@ -
+

Data Export

- - -
-

Your Data, Your Control

-

- Export a complete backup of your LibreFit data. This includes all your tracked meals, weight - entries, targets, and personal settings. -

- -

What's included:

-
    -
  • All calorie tracking entries
  • -
  • Weight history and targets
  • -
  • Your profile and body data
  • -
  • Custom settings and preferences
  • -
-
-
- - {#snippet icon(option)} - {#if option.value === 'raw'} - - {:else if option.value === 'csv'} - - {:else if option.value === 'pdf'} - - {/if} - {/snippet} - + +
+
+
+ Export Data + Back up your LibreFit data +
+ + + +
- + +
+ + Your data, your control + + Export a complete backup of your LibreFit data. This includes all your tracked meals, weight + entries, targets, and personal settings. + + + +
+
+

What's included

+
+
+
+ + All calorie tracking entries +
+
+ + Weight history and targets +
+
+ + Your profile and body data +
+
+ + Custom settings and preferences +
+
+
+ +
+ + {#snippet icon(option)} + {#if option.value === 'raw'} + + {:else if option.value === 'csv'} + + {:else if option.value === 'pdf'} + + {/if} + {/snippet} + +
+ + +
@@ -313,3 +326,20 @@
{/snippet} + + diff --git a/src/routes/(app)/import/+page.svelte b/src/routes/(app)/import/+page.svelte index b630011d..434fc921 100644 --- a/src/routes/(app)/import/+page.svelte +++ b/src/routes/(app)/import/+page.svelte @@ -7,7 +7,6 @@ ImportTableSchema, type ImportResult } from '$lib/api/gen/types'; - import SettingsIcon from '$lib/component/navigation/SettingsIcon.svelte'; import { Channel } from '@tauri-apps/api/core'; import { open } from '@tauri-apps/plugin-dialog'; import { debug, error } from '@tauri-apps/plugin-log'; @@ -15,12 +14,9 @@ AlertBox, AlertType, AlertVariant, - Breadcrumbs, LoadingIndicator, ModalDialog, OptionCards, - TextSize, - type BreadcrumbItem, type OptionCardData } from '@thwbh/veilchen'; import { Check, ForkKnife, Scales, Upload, Warning } from 'phosphor-svelte'; @@ -45,26 +41,12 @@ { value: ImportTable.intake, header: 'Intake', - text: 'Import intake data.' + text: 'Restore your tracked meals and calorie entries from a CSV file.' }, { value: ImportTable.weightTracker, header: 'Weight', - text: 'Import weight tracking data.' - } - ]; - - const items: BreadcrumbItem[] = [ - { - id: '1', - icon: SettingsIcon - }, - { - id: '2', - href: '/import', - label: 'Import Data', - icon: Upload, - iconProps: { weight: 'bold' } + text: 'Restore your weight history and measurements from a CSV file.' } ]; @@ -163,55 +145,84 @@ } -
+

Data Import

- - -
-

Import Existing Data

-

- Import your LibreFit data from CSV files. Choose a valid CSV file and select the target table - to import into. -

- -

Available import targets:

-
    -
  • Calorie tracking entries (Intake)
  • -
  • Weight tracking history
  • -
-
-
- - {#snippet icon(option)} - {#if option.value === ImportTable.intake} - - {:else if option.value === ImportTable.weightTracker} - - {/if} - {/snippet} - + +
+
+
+ Import Data + Restore your data from CSV files +
+ + + +
-
- Pick a file -
- - + +
+ + Import existing data + + Import your LibreFit data from CSV files. Choose a valid CSV file and select the target + table to import into. + + + +
+
+

+ Available import targets +

+
+
+
+ + Meal data (Intake) +
+
+ + Weight tracking history +
+
+
+ +
+ + {#snippet icon(option)} + {#if option.value === ImportTable.intake} + + {:else if option.value === ImportTable.weightTracker} + + {/if} + {/snippet} +
-
- - Important: Importing the same file multiple times will create duplicate entries. - There is no automatic deduplication. - +
+ Pick a file +
+ + +
+
+ + + Important: Importing the same file multiple times will create duplicate entries. + There is no automatic deduplication. + - + +
@@ -290,3 +301,20 @@
{/snippet} + + diff --git a/src/routes/(app)/profile/+page.svelte b/src/routes/(app)/profile/+page.svelte index c648e8a6..55023d63 100644 --- a/src/routes/(app)/profile/+page.svelte +++ b/src/routes/(app)/profile/+page.svelte @@ -1,13 +1,5 @@ -
+

User Profile

- -
+ +
+
+
+ Profile + Manage your personal information +
+ + + +
+
+ + +
openEdit()}> {#snippet leftAction()} diff --git a/src/routes/(app)/progress/+page.svelte b/src/routes/(app)/progress/+page.svelte index 5bc03b89..5479e5eb 100644 --- a/src/routes/(app)/progress/+page.svelte +++ b/src/routes/(app)/progress/+page.svelte @@ -1,7 +1,7 @@ -
- +
+

Setup Wizard

+ + +
+
+
+ Step {currentStep} of {totalSteps} + {currentConfig.title} + {currentConfig.subtitle} +
+ + {#if currentConfig.icon} + {@const Icon = currentConfig.icon} + + + + {/if} +
- + +
+
+
+
+
+
+ + +
+ +
+ + diff --git a/src/routes/setup/+page.svelte b/src/routes/setup/+page.svelte index c835193f..33f79380 100644 --- a/src/routes/setup/+page.svelte +++ b/src/routes/setup/+page.svelte @@ -1,7 +1,106 @@ -
- +
+

Setup Wizard

+ + +
+
+
+ Step {currentStep} of {totalSteps} + {currentConfig.title} + {currentConfig.subtitle} +
+ + {#if currentConfig.icon} + {@const Icon = currentConfig.icon} + + + + {/if} +
+ + +
+
+
+
+
+
+ + +
+ +
+ + diff --git a/src/routes/welcome/+page.svelte b/src/routes/welcome/+page.svelte index 6e7c0fb8..09955307 100644 --- a/src/routes/welcome/+page.svelte +++ b/src/routes/welcome/+page.svelte @@ -22,7 +22,7 @@
-
+
{#if mounted}
diff --git a/src/style.css b/src/style.css index 88170110..0cacb30a 100644 --- a/src/style.css +++ b/src/style.css @@ -41,3 +41,11 @@ --depth: 1; --noise: 1; } + +/* Safe area insets for devices with notches / rounded corners */ +body { + padding-top: env(safe-area-inset-top); + padding-bottom: env(safe-area-inset-bottom); + padding-left: env(safe-area-inset-left); + padding-right: env(safe-area-inset-right); +}