diff --git a/mobile/DESIGN_SYSTEM.md b/mobile/DESIGN_SYSTEM.md new file mode 100644 index 00000000..13a4b41e --- /dev/null +++ b/mobile/DESIGN_SYSTEM.md @@ -0,0 +1,696 @@ +# BottleCRM Mobile Design System + +A comprehensive design guide for the BottleCRM mobile app, inspired by modern CRM apps like HubSpot. + +--- + +## Table of Contents + +1. [Design Principles](#design-principles) +2. [Color Palette](#color-palette) +3. [Typography](#typography) +4. [Spacing & Layout](#spacing--layout) +5. [Components](#components) +6. [Icons](#icons) +7. [Patterns](#patterns) +8. [Accessibility](#accessibility) + +--- + +## Design Principles + +### Core Values + +1. **Clarity First** - Every element should have a clear purpose +2. **Scannable** - Users should quickly find what they need +3. **Minimal Friction** - Reduce taps to complete actions +4. **Consistent** - Same patterns across all screens +5. **Professional** - Clean, modern, business-appropriate + +### Design Philosophy + +- **Flat Design** - No heavy shadows, use borders for depth +- **White Space** - Generous spacing for readability +- **High Contrast** - Clear text hierarchy +- **Touch Friendly** - Minimum 44px touch targets + +--- + +## Color Palette + +### Primary Colors + +| Name | Hex | Usage | +|------|-----|-------| +| **Primary 600** | `#2563EB` | Primary actions, links, selected states | +| **Primary 500** | `#3B82F6` | Hover states, secondary emphasis | +| **Primary 100** | `#DBEAFE` | Light backgrounds, chips | +| **Primary 50** | `#EFF6FF` | Subtle backgrounds | + +### Accent Colors + +| Name | Hex | Usage | +|------|-----|-------| +| **Accent** | `#F97316` | FAB, important CTAs, notifications | +| **Accent Light** | `#FED7AA` | Accent backgrounds, highlights | + +### Semantic Colors + +| Name | Hex | Usage | +|------|-----|-------| +| **Success 500** | `#22C55E` | Won deals, completed tasks | +| **Success 100** | `#DCFCE7` | Success backgrounds | +| **Warning 500** | `#F59E0B` | Attention needed, medium priority | +| **Warning 100** | `#FEF3C7` | Warning backgrounds | +| **Danger 500** | `#EF4444` | Errors, overdue, lost deals | +| **Danger 100** | `#FEE2E2` | Error backgrounds | + +### Neutral Colors + +| Name | Hex | Usage | +|------|-----|-------| +| **Gray 900** | `#0F172A` | Primary text | +| **Gray 700** | `#374151` | Secondary text | +| **Gray 500** | `#6B7280` | Tertiary text, placeholders | +| **Gray 300** | `#D1D5DB` | Borders, dividers | +| **Gray 200** | `#E5E7EB` | Card borders, separators | +| **Gray 100** | `#F3F4F6` | Backgrounds, disabled states | +| **Gray 50** | `#F9FAFB` | Page background | +| **White** | `#FFFFFF` | Cards, surfaces | + +### Status Colors for CRM + +``` +Lead Status: +- New → Primary 500 (#3B82F6) +- Contacted → Warning 500 (#F59E0B) +- Qualified → Success 500 (#22C55E) +- Lost → Danger 500 (#EF4444) + +Deal Stages: +- Prospecting → Gray 400 (#9CA3AF) +- Qualified → Primary 500 (#3B82F6) +- Proposal → Purple 500 (#A855F7) +- Negotiation → Warning 500 (#F59E0B) +- Closed Won → Success 500 (#22C55E) +- Closed Lost → Danger 500 (#EF4444) + +Priority: +- Low → Gray 400 (#9CA3AF) +- Medium → Warning 500 (#F59E0B) +- High → Danger 500 (#EF4444) +- Urgent → Purple 500 (#A855F7) +``` + +--- + +## Typography + +### Font Family + +**Primary Font:** Inter (Google Fonts) +- Designed for screens +- Excellent readability at small sizes +- 9 weights available +- Free and open source + +**Fallback:** System fonts (SF Pro on iOS, Roboto on Android) + +### Type Scale + +| Style | Size | Weight | Line Height | Letter Spacing | Usage | +|-------|------|--------|-------------|----------------|-------| +| **Display** | 28px | Bold (700) | 1.2 | -0.5px | KPIs, large numbers | +| **H1** | 22px | Semi-bold (600) | 1.3 | -0.3px | Page titles | +| **H2** | 18px | Semi-bold (600) | 1.4 | -0.2px | Section headers | +| **H3** | 16px | Semi-bold (600) | 1.4 | 0 | Card titles, list item titles | +| **Body Large** | 16px | Regular (400) | 1.5 | 0 | Important body text | +| **Body** | 15px | Regular (400) | 1.5 | 0 | Default body text | +| **Body Small** | 14px | Regular (400) | 1.4 | 0 | Secondary text | +| **Caption** | 12px | Regular (400) | 1.3 | 0 | Timestamps, helper text | +| **Overline** | 11px | Semi-bold (600) | 1.3 | 0.5px | Section labels (uppercase) | + +### Number Typography + +| Style | Size | Weight | Usage | +|-------|------|--------|-------| +| **Number Large** | 28px | Bold (700) | Dashboard KPIs | +| **Number Medium** | 20px | Semi-bold (600) | Card metrics | +| **Number Small** | 14px | Medium (500) | Inline numbers | + +Use `FontFeature.tabularFigures()` for aligned numbers in tables/lists. + +### Text Colors + +| Type | Color | Hex | +|------|-------|-----| +| Primary | Gray 900 | `#0F172A` | +| Secondary | Gray 500 | `#6B7280` | +| Tertiary | Gray 400 | `#9CA3AF` | +| Disabled | Gray 400 @ 50% | `#9CA3AF80` | +| Link | Primary 600 | `#2563EB` | +| Error | Danger 500 | `#EF4444` | + +--- + +## Spacing & Layout + +### Spacing Scale (4px base) + +| Token | Value | Usage | +|-------|-------|-------| +| `xs` | 4px | Tight gaps, icon padding | +| `sm` | 8px | Small gaps, list item spacing | +| `md` | 12px | Default component padding | +| `lg` | 16px | Section padding, card padding | +| `xl` | 20px | Large gaps | +| `xxl` | 24px | Section spacing | +| `xxxl` | 32px | Page sections | + +### Page Layout + +``` +┌─────────────────────────────────────┐ +│ Status Bar (system) │ +├─────────────────────────────────────┤ +│ App Bar (52-56px) │ +│ ← Title Actions │ +├─────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────┐ │ +│ │ Content Area │ │ +│ │ Padding: 16px horizontal │ │ +│ │ │ │ +│ │ Section Gap: 20-24px │ │ +│ │ │ │ +│ └─────────────────────────────┘ │ +│ │ +│ ┌───┐ │ +│ │FAB│ │ +│ └───┘ │ +├─────────────────────────────────────┤ +│ Bottom Navigation (56-60px) │ +│ Home Contacts Deals Tasks More │ +└─────────────────────────────────────┘ +``` + +### Component Heights + +| Component | Height | +|-----------|--------| +| App Bar | 52-56px | +| Bottom Nav | 56-60px | +| Button Large | 48px | +| Button Medium | 44px | +| Button Small | 36px | +| Input Field | 48px | +| List Item | 64-72px | +| Quick Action Button | 56px | +| FAB | 56px | +| Tab Bar | 48px | +| Search Bar | 44px | +| Filter Chip | 36px | + +### Border Radius + +| Size | Value | Usage | +|------|-------|-------| +| XS | 4px | Small elements, inputs | +| SM | 6px | Buttons, chips | +| MD | 8px | Cards, dialogs | +| LG | 12px | Large cards, modals | +| XL | 16px | Bottom sheets | +| Full | 9999px | Pills, avatars, FAB | + +--- + +## Components + +### Cards + +``` +┌─────────────────────────────────────┐ +│ Card Title Action ⋮ │ ← 16px padding +│ Subtitle or count │ +├─────────────────────────────────────┤ +│ │ +│ Card Content │ +│ │ +└─────────────────────────────────────┘ + +Style: +- Background: White +- Border: 1px solid Gray 200 +- Border Radius: 8px +- Shadow: NONE (flat design) +- Padding: 16px +``` + +### List Items + +``` +┌─────────────────────────────────────┐ +│ ○ Title Text ⋮ │ +│ Subtitle or description │ +│ 📅 Date/time [Badge] │ +└─────────────────────────────────────┘ + +Style: +- Height: 64-72px +- Padding: 16px horizontal, 12px vertical +- Border Bottom: 1px solid Gray 200 +- Icon/Avatar: 40px, left aligned +- Action: Right aligned +``` + +### Buttons + +**Primary Button (Filled)** +``` +Background: Primary 600 +Text: White, 15px Semi-bold +Height: 48px +Border Radius: 8px +Padding: 16px horizontal +``` + +**Secondary Button (Outlined)** +``` +Background: Transparent +Border: 1px solid Gray 300 +Text: Gray 900, 15px Semi-bold +Height: 48px +Border Radius: 8px +``` + +**Text Button** +``` +Background: Transparent +Text: Primary 600, 15px Semi-bold +Height: 44px +Padding: 8px horizontal +``` + +**FAB (Floating Action Button)** +``` +Background: Accent (#F97316) +Icon: White, 24px +Size: 56px +Border Radius: 16px (or full) +Shadow: Subtle elevation +Position: Bottom-right, 16px from edges +``` + +### Quick Action Buttons + +``` +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ 🏠 │ │ 📊 │ │ 👥 │ │ 📞 │ +│ Feed │ │Dashboard│ │ Leads │ │ Calls │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ + +Style: +- Size: 72px width, 56px height +- Background: White +- Border: 1px solid Gray 200 +- Border Radius: 8px +- Icon: 24px, Gray 700 +- Label: 11px, Gray 500 +- Gap between: 12px +- Horizontal scroll if overflow +``` + +### Chips & Badges + +**Filter Chip** +``` +Height: 36px +Padding: 12px horizontal +Border: 1px solid Primary 600 +Border Radius: 18px (full) +Text: Primary 600, 13px Medium +Background: Transparent (or Primary 50 when selected) +``` + +**Status Badge** +``` +Height: 22px +Padding: 8px horizontal +Border Radius: 11px (full) +Text: 11px Semi-bold, White +Background: Status color +``` + +**Contact Chip** +``` +Height: 32px +Padding: 12px horizontal +Border Radius: 16px +Background: Primary 100 +Text: Primary 700, 13px Medium +``` + +### Bottom Navigation + +``` +┌─────────────────────────────────────┐ +│ 🏠 👤 💼 ✓ 🔍 │ +│ Home Contacts Deals Tasks Search │ +└─────────────────────────────────────┘ + +Style: +- Height: 56-60px +- Background: White +- Border Top: 1px solid Gray 200 +- Icon: 24px +- Label: 11px +- Active: Primary 600 +- Inactive: Gray 400 +- 5 items maximum +``` + +### Input Fields + +``` +┌─────────────────────────────────────┐ +│ Label │ +├─────────────────────────────────────┤ +│ Placeholder text... │ +└─────────────────────────────────────┘ + +Style: +- Height: 48px +- Background: White +- Border: 1px solid Gray 300 +- Border Radius: 8px +- Padding: 16px horizontal +- Label: 13px Medium, Gray 700 +- Input Text: 15px Regular, Gray 900 +- Placeholder: 15px Regular, Gray 400 +- Focus Border: Primary 600 +- Error Border: Danger 500 +``` + +### Section Header + +``` +Guided actions View all → +Subtitle text here + +Style: +- Title: 16px Semi-bold, Gray 900 +- Subtitle: 13px Regular, Gray 500 +- Link: 14px Medium, Primary 600 +- Margin Bottom: 12px +``` + +--- + +## Icons + +### Icon Sizes + +| Size | Pixels | Usage | +|------|--------|-------| +| XS | 16px | Inline, badges | +| SM | 20px | List items, buttons | +| MD | 24px | Default, nav items | +| LG | 28px | Emphasis | +| XL | 32px | Empty states | +| XXL | 48px | Illustrations | + +### Icon Style + +- **Library:** Lucide Icons (consistent with web app) +- **Stroke Width:** 1.5-2px +- **Style:** Outlined (not filled) +- **Color:** Inherit from text or specific semantic color + +### Common Icons + +``` +Navigation: +- Home: home +- Contacts: users +- Deals: briefcase +- Tasks: check-square +- Search: search +- Settings: settings +- More: more-horizontal + +Actions: +- Add: plus +- Edit: pencil +- Delete: trash-2 +- Share: share +- Filter: filter +- Sort: arrow-up-down +- Refresh: refresh-cw + +Communication: +- Email: mail +- Call: phone +- Meeting: calendar +- Note: file-text + +Status: +- Success: check-circle +- Warning: alert-triangle +- Error: x-circle +- Info: info +``` + +--- + +## Patterns + +### Dashboard Layout + +``` +┌─────────────────────────────────────┐ +│ Page Title │ +├─────────────────────────────────────┤ +│ [Quick Actions Row - Horizontal] │ +├─────────────────────────────────────┤ +│ Section Header View all│ +│ ┌─────────────────────────────────┐ │ +│ │ Metric Card / Chart │ │ +│ └─────────────────────────────────┘ │ +├─────────────────────────────────────┤ +│ Section Header View all│ +│ ┌─────────────────────────────────┐ │ +│ │ List Item 1 │ │ +│ ├─────────────────────────────────┤ │ +│ │ List Item 2 │ │ +│ ├─────────────────────────────────┤ │ +│ │ List Item 3 │ │ +│ └─────────────────────────────────┘ │ +└─────────────────────────────────────┘ +``` + +### List View Layout + +``` +┌─────────────────────────────────────┐ +│ ← Page Title 🔍 ⋮ │ +├─────────────────────────────────────┤ +│ [Filter Chips - Horizontal Scroll] │ +├─────────────────────────────────────┤ +│ ┌─────────────────────────────────┐ │ +│ │ List Item with Avatar │ │ +│ │ Title → │ │ +│ │ Subtitle Status Badge │ │ +│ └─────────────────────────────────┘ │ +│ ┌─────────────────────────────────┐ │ +│ │ List Item │ │ +│ └─────────────────────────────────┘ │ +│ [+ FAB] │ +└─────────────────────────────────────┘ +``` + +### Detail View Layout + +``` +┌─────────────────────────────────────┐ +│ ← Back Title Edit ⋮ │ +├─────────────────────────────────────┤ +│ │ +│ [Avatar / Icon - 64px] │ +│ Primary Name │ +│ Secondary Info │ +│ │ +│ ┌─────────────────────────────────┐ │ +│ │ [Action Buttons Row] │ │ +│ │ 📧 Email 📞 Call 📝 Note │ │ +│ └─────────────────────────────────┘ │ +├─────────────────────────────────────┤ +│ [Tab Bar: Details | Activity | ... ]│ +├─────────────────────────────────────┤ +│ │ +│ Field Label │ +│ Field Value │ +│ ─────────────────────────────────── │ +│ Field Label │ +│ Field Value │ +│ │ +└─────────────────────────────────────┘ +``` + +### Form Layout + +``` +┌─────────────────────────────────────┐ +│ ← Cancel Title Save │ +├─────────────────────────────────────┤ +│ │ +│ Section Label │ +│ ┌─────────────────────────────────┐ │ +│ │ Input Field │ │ +│ └─────────────────────────────────┘ │ +│ ┌─────────────────────────────────┐ │ +│ │ Input Field │ │ +│ └─────────────────────────────────┘ │ +│ │ +│ Section Label │ +│ ┌─────────────────────────────────┐ │ +│ │ Dropdown / Picker │ │ +│ └─────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────┘ + +Spacing: +- Section gap: 24px +- Field gap: 16px +- Label to field: 8px +``` + +### Empty State + +``` +┌─────────────────────────────────────┐ +│ │ +│ │ +│ [Icon - 48px] │ +│ │ +│ No items yet │ +│ Description text goes here │ +│ explaining what to do next │ +│ │ +│ [ Primary Action ] │ +│ │ +│ │ +└─────────────────────────────────────┘ + +Style: +- Icon: Gray 300, 48px +- Title: 18px Semi-bold, Gray 700 +- Description: 14px Regular, Gray 500, centered +- Max width: 280px +``` + +### Loading States + +**Skeleton Loading** +``` +Use animated placeholder shapes matching content layout: +- Rectangles for text (with rounded corners) +- Circles for avatars +- Animation: Shimmer effect (light gray pulse) +``` + +**Inline Loading** +``` +- Small spinner (20px) next to action +- Or three-dot animation +``` + +**Full Screen Loading** +``` +- Centered spinner (40px) +- Optional: Brand logo animation +``` + +--- + +## Accessibility + +### Minimum Requirements + +1. **Touch Targets:** Minimum 44x44px for all interactive elements +2. **Color Contrast:** 4.5:1 ratio for normal text, 3:1 for large text +3. **Focus Indicators:** Visible focus states for keyboard navigation +4. **Screen Readers:** Proper semantic labels and hints + +### Color Contrast Ratios + +| Combination | Ratio | Status | +|-------------|-------|--------| +| Gray 900 on White | 15.5:1 | ✅ AAA | +| Gray 500 on White | 4.6:1 | ✅ AA | +| Primary 600 on White | 4.5:1 | ✅ AA | +| White on Primary 600 | 4.5:1 | ✅ AA | +| White on Accent | 3.1:1 | ⚠️ AA Large | + +### Text Scaling + +- Support system text scaling up to 200% +- Use flexible layouts that accommodate larger text +- Test with accessibility settings enabled + +--- + +## Implementation Notes + +### Flutter Specifics + +```dart +// Theme setup +ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.light( + primary: Color(0xFF2563EB), + secondary: Color(0xFFF97316), + surface: Colors.white, + background: Color(0xFFF9FAFB), + error: Color(0xFFEF4444), + ), + textTheme: GoogleFonts.interTextTheme(), +) + +// No elevation/shadows on cards +Card( + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: Color(0xFFE5E7EB)), + ), +) +``` + +### Do's and Don'ts + +**Do:** +- Use consistent spacing from the scale +- Keep cards flat with borders +- Use semantic colors for status +- Maintain touch target sizes +- Test on multiple screen sizes + +**Don't:** +- Use shadows on cards (flat design) +- Mix different border radius sizes +- Use colors outside the palette +- Create touch targets smaller than 44px +- Hardcode pixel values (use tokens) + +--- + +## Version History + +| Version | Date | Changes | +|---------|------|---------| +| 1.0 | Jan 2026 | Initial design system based on HubSpot mobile app analysis | + +--- + +*This design system is a living document. Update as the app evolves.* diff --git a/mobile/lib/core/theme/app_colors.dart b/mobile/lib/core/theme/app_colors.dart index b62f860a..8b69d411 100644 --- a/mobile/lib/core/theme/app_colors.dart +++ b/mobile/lib/core/theme/app_colors.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; /// BottleCRM Color System -/// A professional, modern color palette optimized for sales workflows +/// A professional, modern color palette optimized for CRM workflows +/// Inspired by HubSpot mobile app design patterns class AppColors { AppColors._(); @@ -19,6 +20,18 @@ class AppColors { static const Color primary800 = Color(0xFF1E40AF); static const Color primary900 = Color(0xFF1E3A8A); + // ============================================ + // ACCENT PALETTE (Coral/Orange - CTAs & FAB) + // ============================================ + static const Color accent50 = Color(0xFFFFF7ED); + static const Color accent100 = Color(0xFFFFEDD5); + static const Color accent200 = Color(0xFFFED7AA); + static const Color accent300 = Color(0xFFFDBA74); + static const Color accent400 = Color(0xFFFB923C); + static const Color accent500 = Color(0xFFF97316); // Main accent color + static const Color accent600 = Color(0xFFEA580C); + static const Color accent700 = Color(0xFFC2410C); + // ============================================ // SUCCESS (Green - Wins & Completions) // ============================================ @@ -71,6 +84,18 @@ class AppColors { static const Color purple600 = Color(0xFF9333EA); static const Color purple700 = Color(0xFF7C3AED); + // ============================================ + // TEAL (For Links & Dates - HubSpot style) + // ============================================ + static const Color teal50 = Color(0xFFF0FDFA); + static const Color teal100 = Color(0xFFCCFBF1); + static const Color teal200 = Color(0xFF99F6E4); + static const Color teal300 = Color(0xFF5EEAD4); + static const Color teal400 = Color(0xFF2DD4BF); + static const Color teal500 = Color(0xFF14B8A6); + static const Color teal600 = Color(0xFF0D9488); + static const Color teal700 = Color(0xFF0F766E); + // ============================================ // NEUTRAL GRAYS // ============================================ @@ -89,7 +114,7 @@ class AppColors { // SURFACES // ============================================ static const Color surface = Color(0xFFFFFFFF); - static const Color surfaceDim = Color(0xFFF8FAFC); + static const Color surfaceDim = Color(0xFFF9FAFB); // Page background static const Color surfaceBright = Color(0xFFFFFFFF); // ============================================ @@ -131,6 +156,7 @@ class AppColors { static const Color priorityLow = gray400; static const Color priorityMedium = warning500; static const Color priorityHigh = danger500; + static const Color priorityUrgent = purple500; // ============================================ // AVATAR BACKGROUND COLORS diff --git a/mobile/lib/core/theme/app_theme.dart b/mobile/lib/core/theme/app_theme.dart index 2bc8a598..0d8c94cc 100644 --- a/mobile/lib/core/theme/app_theme.dart +++ b/mobile/lib/core/theme/app_theme.dart @@ -110,16 +110,16 @@ class AppTheme { }), ), - // Floating Action Button + // Floating Action Button (Accent color like HubSpot) floatingActionButtonTheme: FloatingActionButtonThemeData( - backgroundColor: AppColors.primary600, + backgroundColor: AppColors.accent500, foregroundColor: Colors.white, - elevation: 4, - focusElevation: 6, - hoverElevation: 6, - highlightElevation: 8, + elevation: 2, + focusElevation: 4, + hoverElevation: 4, + highlightElevation: 4, shape: RoundedRectangleBorder( - borderRadius: AppLayout.borderRadiusLg, + borderRadius: BorderRadius.circular(16), ), ), @@ -214,13 +214,14 @@ class AppTheme { suffixIconColor: AppColors.gray400, ), - // Card + // Card (Flat design with border, no shadow) cardTheme: CardThemeData( color: AppColors.card, elevation: 0, margin: EdgeInsets.zero, shape: RoundedRectangleBorder( - borderRadius: AppLayout.borderRadiusLg, + borderRadius: AppLayout.borderRadiusMd, + side: const BorderSide(color: AppColors.border, width: 1), ), ), diff --git a/mobile/lib/core/theme/app_typography.dart b/mobile/lib/core/theme/app_typography.dart index cb09f49c..40f8c531 100644 --- a/mobile/lib/core/theme/app_typography.dart +++ b/mobile/lib/core/theme/app_typography.dart @@ -3,8 +3,8 @@ import 'package:google_fonts/google_fonts.dart'; import 'app_colors.dart'; /// BottleCRM Typography System -/// Using Plus Jakarta Sans - a modern, professional geometric sans-serif -/// that's distinctive yet highly readable for data-dense interfaces +/// Using Inter - a font designed specifically for screens +/// with excellent readability at small sizes class AppTypography { AppTypography._(); @@ -12,10 +12,10 @@ class AppTypography { // DISPLAY & HEADINGS // ============================================ - /// Display - Hero text, large numbers - /// 32px, Bold, 1.2 line height - static TextStyle get display => GoogleFonts.plusJakartaSans( - fontSize: 32, + /// Display - KPIs, large numbers, hero text + /// 28px, Bold, 1.2 line height + static TextStyle get display => GoogleFonts.inter( + fontSize: 28, fontWeight: FontWeight.w700, height: 1.2, color: AppColors.textPrimary, @@ -23,19 +23,19 @@ class AppTypography { ); /// H1 - Main page titles - /// 24px, Bold, 1.3 line height - static TextStyle get h1 => GoogleFonts.plusJakartaSans( - fontSize: 24, - fontWeight: FontWeight.w700, + /// 22px, Semi-bold, 1.3 line height + static TextStyle get h1 => GoogleFonts.inter( + fontSize: 22, + fontWeight: FontWeight.w600, height: 1.3, color: AppColors.textPrimary, letterSpacing: -0.3, ); /// H2 - Section titles - /// 20px, Semi-bold, 1.4 line height - static TextStyle get h2 => GoogleFonts.plusJakartaSans( - fontSize: 20, + /// 18px, Semi-bold, 1.4 line height + static TextStyle get h2 => GoogleFonts.inter( + fontSize: 18, fontWeight: FontWeight.w600, height: 1.4, color: AppColors.textPrimary, @@ -43,9 +43,9 @@ class AppTypography { ); /// H3 - Card titles, subsections - /// 18px, Semi-bold, 1.4 line height - static TextStyle get h3 => GoogleFonts.plusJakartaSans( - fontSize: 18, + /// 16px, Semi-bold, 1.4 line height + static TextStyle get h3 => GoogleFonts.inter( + fontSize: 16, fontWeight: FontWeight.w600, height: 1.4, color: AppColors.textPrimary, @@ -55,28 +55,28 @@ class AppTypography { // BODY TEXT // ============================================ - /// Body Large - Prominent body text + /// Body Large - Important body text /// 16px, Regular, 1.5 line height - static TextStyle get bodyLarge => GoogleFonts.plusJakartaSans( + static TextStyle get bodyLarge => GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.w400, height: 1.5, color: AppColors.textPrimary, ); - /// Body - Standard body text - /// 14px, Regular, 1.5 line height - static TextStyle get body => GoogleFonts.plusJakartaSans( - fontSize: 14, + /// Body - Default body text + /// 15px, Regular, 1.5 line height + static TextStyle get body => GoogleFonts.inter( + fontSize: 15, fontWeight: FontWeight.w400, height: 1.5, color: AppColors.textPrimary, ); /// Body Small - Secondary body text - /// 12px, Regular, 1.4 line height - static TextStyle get bodySmall => GoogleFonts.plusJakartaSans( - fontSize: 12, + /// 14px, Regular, 1.4 line height + static TextStyle get bodySmall => GoogleFonts.inter( + fontSize: 14, fontWeight: FontWeight.w400, height: 1.4, color: AppColors.textPrimary, @@ -86,19 +86,19 @@ class AppTypography { // LABELS & UI TEXT // ============================================ - /// Label - Input labels, button text - /// 14px, Medium, 1.4 line height - static TextStyle get label => GoogleFonts.plusJakartaSans( - fontSize: 14, + /// Label - Input labels, list item titles + /// 15px, Medium, 1.4 line height + static TextStyle get label => GoogleFonts.inter( + fontSize: 15, fontWeight: FontWeight.w500, height: 1.4, color: AppColors.textPrimary, ); - /// Label Small - Small labels, badges - /// 12px, Medium, 1.3 line height - static TextStyle get labelSmall => GoogleFonts.plusJakartaSans( - fontSize: 12, + /// Label Small - Small labels, nav items + /// 13px, Medium, 1.3 line height + static TextStyle get labelSmall => GoogleFonts.inter( + fontSize: 13, fontWeight: FontWeight.w500, height: 1.3, color: AppColors.textPrimary, @@ -110,17 +110,17 @@ class AppTypography { /// Caption - Timestamps, helper text /// 12px, Regular, 1.3 line height, Secondary color - static TextStyle get caption => GoogleFonts.plusJakartaSans( + static TextStyle get caption => GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w400, height: 1.3, color: AppColors.textSecondary, ); - /// Overline - Section headers, category labels - /// 12px, Semi-bold, 0.5 letter spacing, uppercase - static TextStyle get overline => GoogleFonts.plusJakartaSans( - fontSize: 12, + /// Overline - Section labels, category headers + /// 11px, Semi-bold, 0.5 letter spacing + static TextStyle get overline => GoogleFonts.inter( + fontSize: 11, fontWeight: FontWeight.w600, height: 1.3, letterSpacing: 0.5, @@ -131,9 +131,9 @@ class AppTypography { // NUMBERS & DATA // ============================================ - /// Number Large - KPI values, large amounts + /// Number Large - Dashboard KPIs, large metrics /// 28px, Bold, Tabular figures - static TextStyle get numberLarge => GoogleFonts.plusJakartaSans( + static TextStyle get numberLarge => GoogleFonts.inter( fontSize: 28, fontWeight: FontWeight.w700, height: 1.2, @@ -141,19 +141,19 @@ class AppTypography { fontFeatures: const [FontFeature.tabularFigures()], ); - /// Number - Standard numeric values - /// 16px, Semi-bold, Tabular figures - static TextStyle get number => GoogleFonts.plusJakartaSans( - fontSize: 16, + /// Number Medium - Card metrics + /// 20px, Semi-bold, Tabular figures + static TextStyle get numberMedium => GoogleFonts.inter( + fontSize: 20, fontWeight: FontWeight.w600, height: 1.3, color: AppColors.textPrimary, fontFeatures: const [FontFeature.tabularFigures()], ); - /// Number Small - Small numeric values, percentages + /// Number Small - Inline numbers /// 14px, Medium, Tabular figures - static TextStyle get numberSmall => GoogleFonts.plusJakartaSans( + static TextStyle get numberSmall => GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w500, height: 1.3, @@ -166,9 +166,9 @@ class AppTypography { // ============================================ /// Button Large - Primary action buttons - /// 16px, Semi-bold - static TextStyle get buttonLarge => GoogleFonts.plusJakartaSans( - fontSize: 16, + /// 15px, Semi-bold + static TextStyle get buttonLarge => GoogleFonts.inter( + fontSize: 15, fontWeight: FontWeight.w600, height: 1.0, color: Colors.white, @@ -176,7 +176,7 @@ class AppTypography { /// Button - Standard buttons /// 14px, Semi-bold - static TextStyle get button => GoogleFonts.plusJakartaSans( + static TextStyle get button => GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w600, height: 1.0, @@ -184,14 +184,36 @@ class AppTypography { ); /// Button Small - Compact buttons - /// 12px, Semi-bold - static TextStyle get buttonSmall => GoogleFonts.plusJakartaSans( - fontSize: 12, + /// 13px, Semi-bold + static TextStyle get buttonSmall => GoogleFonts.inter( + fontSize: 13, fontWeight: FontWeight.w600, height: 1.0, color: Colors.white, ); + // ============================================ + // LINK TEXT + // ============================================ + + /// Link - Clickable text links + /// 14px, Medium, Primary color + static TextStyle get link => GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + height: 1.4, + color: AppColors.primary600, + ); + + /// Link Small - Small clickable links (e.g., "View all") + /// 13px, Medium, Primary color + static TextStyle get linkSmall => GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w500, + height: 1.3, + color: AppColors.primary600, + ); + // ============================================ // HELPER METHODS // ============================================ diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 9b391895..9506fe57 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -2,17 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'core/theme/theme.dart'; -import 'providers/settings_provider.dart'; import 'routes/app_router.dart'; import 'services/auth_service.dart'; -import 'services/settings_service.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // Initialize services await AuthService().initialize(); - await SettingsService().initialize(); // Set system UI overlay style SystemChrome.setSystemUIOverlayStyle( @@ -38,48 +35,28 @@ void main() async { } /// BottleCRM - Main Application Widget -class BottleCRMApp extends ConsumerStatefulWidget { +class BottleCRMApp extends ConsumerWidget { const BottleCRMApp({super.key}); @override - ConsumerState createState() => _BottleCRMAppState(); -} - -class _BottleCRMAppState extends ConsumerState { - @override - void initState() { - super.initState(); - // Initialize settings provider from persisted values - WidgetsBinding.instance.addPostFrameCallback((_) { - ref.read(settingsProvider.notifier).initialize(); - }); - } - - @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final router = ref.watch(appRouterProvider); - final fontScale = ref.watch(fontScaleProvider); - return MediaQuery( - data: MediaQuery.of(context).copyWith( - textScaler: TextScaler.linear(fontScale), - ), - child: MaterialApp.router( - title: 'BottleCRM', - debugShowCheckedModeBanner: false, + return MaterialApp.router( + title: 'BottleCRM', + debugShowCheckedModeBanner: false, - // Theme - theme: AppTheme.light, - darkTheme: AppTheme.dark, - themeMode: ThemeMode.light, + // Theme + theme: AppTheme.light, + darkTheme: AppTheme.dark, + themeMode: ThemeMode.light, - // Router - routerConfig: router, + // Router + routerConfig: router, - // Scroll behavior for smooth scrolling - scrollBehavior: const MaterialScrollBehavior().copyWith( - physics: const BouncingScrollPhysics(), - ), + // Scroll behavior for smooth scrolling + scrollBehavior: const MaterialScrollBehavior().copyWith( + physics: const BouncingScrollPhysics(), ), ); } diff --git a/mobile/lib/providers/settings_provider.dart b/mobile/lib/providers/settings_provider.dart deleted file mode 100644 index 8929960c..00000000 --- a/mobile/lib/providers/settings_provider.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../services/settings_service.dart'; - -/// Re-export FontSizeOption for convenience -export '../services/settings_service.dart' show FontSizeOption; - -/// Settings state for the app -class SettingsState { - final FontSizeOption fontSize; - - const SettingsState({ - this.fontSize = FontSizeOption.defaultSize, - }); - - /// Initial state - const SettingsState.initial() : fontSize = FontSizeOption.defaultSize; - - /// Get font scale factor - double get fontScale => fontSize.scale; - - /// Create a copy with updated fields - SettingsState copyWith({ - FontSizeOption? fontSize, - }) { - return SettingsState( - fontSize: fontSize ?? this.fontSize, - ); - } - - @override - String toString() => 'SettingsState(fontSize: ${fontSize.label})'; -} - -/// Notifier for settings state changes -class SettingsNotifier extends StateNotifier { - SettingsNotifier() : super(const SettingsState.initial()); - - final SettingsService _settingsService = SettingsService(); - - /// Initialize settings from storage (call on app launch) - Future initialize() async { - debugPrint('SettingsNotifier: Initializing...'); - - // SettingsService.initialize() should have been called before this - state = state.copyWith( - fontSize: _settingsService.fontSize, - ); - - debugPrint('SettingsNotifier: Initialized with font size: ${state.fontSize.label}'); - } - - /// Set font size preference - Future setFontSize(FontSizeOption size) async { - debugPrint('SettingsNotifier: Setting font size to ${size.label}...'); - - // Update state immediately for live preview - state = state.copyWith(fontSize: size); - - // Persist to storage - await _settingsService.setFontSize(size); - - debugPrint('SettingsNotifier: Font size set to ${size.label}'); - } -} - -/// Provider for settings state -final settingsProvider = StateNotifierProvider((ref) { - return SettingsNotifier(); -}); - -/// Convenience provider for font scale -final fontScaleProvider = Provider((ref) { - return ref.watch(settingsProvider).fontScale; -}); - -/// Convenience provider for current font size option -final fontSizeOptionProvider = Provider((ref) { - return ref.watch(settingsProvider).fontSize; -}); diff --git a/mobile/lib/routes/app_router.dart b/mobile/lib/routes/app_router.dart index f8bd748f..d66472e1 100644 --- a/mobile/lib/routes/app_router.dart +++ b/mobile/lib/routes/app_router.dart @@ -8,7 +8,6 @@ import '../providers/auth_provider.dart'; import '../screens/auth/splash_screen.dart'; import '../screens/auth/login_screen.dart'; import '../screens/auth/forgot_password_screen.dart'; -import '../screens/auth/onboarding_screen.dart'; import '../screens/auth/org_selection_screen.dart'; // Main Screens @@ -36,7 +35,6 @@ class AppRoutes { static const String splash = '/splash'; static const String login = '/login'; static const String forgotPassword = '/forgot-password'; - static const String onboarding = '/onboarding'; static const String orgSelection = '/org-selection'; // Main routes @@ -66,7 +64,6 @@ const _publicRoutes = [ AppRoutes.splash, AppRoutes.login, AppRoutes.forgotPassword, - AppRoutes.onboarding, ]; /// Routes that require authentication but not org selection @@ -140,11 +137,6 @@ final appRouterProvider = Provider((ref) { name: 'forgotPassword', builder: (context, state) => const ForgotPasswordScreen(), ), - GoRoute( - path: AppRoutes.onboarding, - name: 'onboarding', - builder: (context, state) => const OnboardingScreen(), - ), GoRoute( path: AppRoutes.orgSelection, name: 'orgSelection', diff --git a/mobile/lib/screens/auth/onboarding_screen.dart b/mobile/lib/screens/auth/onboarding_screen.dart deleted file mode 100644 index 33696f3f..00000000 --- a/mobile/lib/screens/auth/onboarding_screen.dart +++ /dev/null @@ -1,325 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:lucide_icons/lucide_icons.dart'; -import '../../core/theme/theme.dart'; -import '../../routes/app_router.dart'; -import '../../widgets/common/common.dart'; - -/// Onboarding Screen -/// 3-slide feature introduction carousel -class OnboardingScreen extends StatefulWidget { - const OnboardingScreen({super.key}); - - @override - State createState() => _OnboardingScreenState(); -} - -class _OnboardingScreenState extends State { - final PageController _pageController = PageController(); - int _currentPage = 0; - - final List<_OnboardingSlide> _slides = [ - _OnboardingSlide( - icon: LucideIcons.users, - iconColor: AppColors.primary600, - backgroundColor: AppColors.primary100, - title: 'Track Your Leads', - description: - 'Manage and organize all your leads in one place. Never miss a follow-up again.', - ), - _OnboardingSlide( - icon: LucideIcons.trendingUp, - iconColor: AppColors.success600, - backgroundColor: AppColors.success100, - title: 'Close More Deals', - description: - 'Visualize your sales pipeline and move deals through stages effortlessly.', - ), - _OnboardingSlide( - icon: LucideIcons.calendar, - iconColor: AppColors.warning600, - backgroundColor: AppColors.warning100, - title: 'Stay Organized', - description: - 'Keep track of tasks, meetings, and deadlines. Boost your productivity.', - ), - ]; - - void _goToNextPage() { - if (_currentPage < _slides.length - 1) { - _pageController.nextPage( - duration: AppDurations.slow, - curve: AppCurves.defaultCurve, - ); - } else { - _completeOnboarding(); - } - } - - void _completeOnboarding() { - context.go(AppRoutes.login); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.surface, - body: SafeArea( - child: Column( - children: [ - // Skip Button - Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.all(16), - child: GestureDetector( - onTap: _completeOnboarding, - child: Text( - 'Skip', - style: AppTypography.label.copyWith( - color: AppColors.textSecondary, - ), - ), - ), - ), - ), - - // Page View - Expanded( - child: PageView.builder( - controller: _pageController, - onPageChanged: (index) { - setState(() => _currentPage = index); - }, - itemCount: _slides.length, - itemBuilder: (context, index) { - return _buildSlide(_slides[index], index); - }, - ), - ), - - // Bottom Section - Padding( - padding: const EdgeInsets.all(24), - child: Column( - children: [ - // Dot Indicators - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate( - _slides.length, - (index) => _buildDot(index), - ), - ), - - const SizedBox(height: 32), - - // Action Button - PrimaryButton( - label: _currentPage == _slides.length - 1 - ? 'Get Started' - : 'Next', - onPressed: _goToNextPage, - icon: _currentPage == _slides.length - 1 - ? LucideIcons.arrowRight - : null, - iconRight: true, - ), - ], - ), - ), - ], - ), - ), - ); - } - - Widget _buildSlide(_OnboardingSlide slide, int index) { - return TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - duration: AppDurations.slow, - builder: (context, value, child) { - return Opacity( - opacity: value, - child: Transform.translate( - offset: Offset(0, 20 * (1 - value)), - child: child, - ), - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Illustration - _buildIllustration(slide), - - const SizedBox(height: 48), - - // Title - Text( - slide.title, - style: AppTypography.h1.copyWith( - fontSize: 28, - ), - textAlign: TextAlign.center, - ), - - const SizedBox(height: 16), - - // Description - Text( - slide.description, - style: AppTypography.body.copyWith( - color: AppColors.textSecondary, - height: 1.6, - ), - textAlign: TextAlign.center, - ), - ], - ), - ), - ); - } - - Widget _buildIllustration(_OnboardingSlide slide) { - return Stack( - alignment: Alignment.center, - children: [ - // Background decorative circles - Container( - width: 280, - height: 280, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: slide.backgroundColor.withValues(alpha: 0.3), - ), - ), - Container( - width: 220, - height: 220, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: slide.backgroundColor.withValues(alpha: 0.5), - ), - ), - // Main circle with icon - Container( - width: 160, - height: 160, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: slide.backgroundColor, - boxShadow: [ - BoxShadow( - color: slide.iconColor.withValues(alpha: 0.3), - blurRadius: 30, - offset: const Offset(0, 10), - ), - ], - ), - child: Icon( - slide.icon, - size: 72, - color: slide.iconColor, - ), - ), - // Floating accent elements - Positioned( - top: 40, - right: 50, - child: _buildFloatingElement( - size: 20, - color: slide.iconColor.withValues(alpha: 0.6), - ), - ), - Positioned( - bottom: 60, - left: 40, - child: _buildFloatingElement( - size: 16, - color: slide.iconColor.withValues(alpha: 0.4), - ), - ), - Positioned( - top: 80, - left: 60, - child: _buildFloatingElement( - size: 12, - color: slide.iconColor.withValues(alpha: 0.5), - isSquare: true, - ), - ), - ], - ); - } - - Widget _buildFloatingElement({ - required double size, - required Color color, - bool isSquare = false, - }) { - return TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - duration: const Duration(milliseconds: 1500), - curve: Curves.elasticOut, - builder: (context, value, child) { - return Transform.scale( - scale: value, - child: child, - ); - }, - child: Container( - width: size, - height: size, - decoration: BoxDecoration( - color: color, - borderRadius: - isSquare ? BorderRadius.circular(4) : BorderRadius.circular(size), - ), - ), - ); - } - - Widget _buildDot(int index) { - final isActive = index == _currentPage; - - return GestureDetector( - onTap: () { - _pageController.animateToPage( - index, - duration: AppDurations.slow, - curve: AppCurves.defaultCurve, - ); - }, - child: AnimatedContainer( - duration: AppDurations.normal, - curve: AppCurves.defaultCurve, - margin: const EdgeInsets.symmetric(horizontal: 4), - height: 8, - width: isActive ? 24 : 8, - decoration: BoxDecoration( - color: isActive ? AppColors.primary600 : AppColors.gray300, - borderRadius: BorderRadius.circular(4), - ), - ), - ); - } -} - -class _OnboardingSlide { - final IconData icon; - final Color iconColor; - final Color backgroundColor; - final String title; - final String description; - - const _OnboardingSlide({ - required this.icon, - required this.iconColor, - required this.backgroundColor, - required this.title, - required this.description, - }); -} diff --git a/mobile/lib/screens/auth/splash_screen.dart b/mobile/lib/screens/auth/splash_screen.dart index 72da6648..05ed6019 100644 --- a/mobile/lib/screens/auth/splash_screen.dart +++ b/mobile/lib/screens/auth/splash_screen.dart @@ -82,8 +82,8 @@ class _SplashScreenState extends ConsumerState context.go(AppRoutes.dashboard); } } else { - // User is not logged in, go to onboarding/login - context.go(AppRoutes.onboarding); + // User is not logged in, go to login + context.go(AppRoutes.login); } } diff --git a/mobile/lib/screens/settings/more_screen.dart b/mobile/lib/screens/settings/more_screen.dart index d7720564..2463ed44 100644 --- a/mobile/lib/screens/settings/more_screen.dart +++ b/mobile/lib/screens/settings/more_screen.dart @@ -6,7 +6,6 @@ import '../../core/theme/theme.dart'; import '../../data/models/models.dart'; import '../../data/mock/mock_data.dart'; import '../../providers/auth_provider.dart'; -import '../../providers/settings_provider.dart'; import '../../routes/app_router.dart'; import '../../widgets/common/common.dart'; @@ -76,10 +75,6 @@ class _MoreScreenState extends ConsumerState { ); }, ), - _FontSizeMenuItem( - currentSize: ref.watch(fontSizeOptionProvider), - onTap: () => _showFontSizePicker(), - ), _MenuItem( icon: LucideIcons.globe, label: 'Language', @@ -310,47 +305,6 @@ class _MoreScreenState extends ConsumerState { ); } - void _showFontSizePicker() { - final currentSize = ref.read(fontSizeOptionProvider); - - showModalBottomSheet( - context: context, - backgroundColor: AppColors.surface, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(20)), - ), - builder: (sheetContext) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - margin: const EdgeInsets.only(top: 12), - width: 40, - height: 4, - decoration: BoxDecoration( - color: AppColors.gray300, - borderRadius: BorderRadius.circular(2), - ), - ), - Padding( - padding: const EdgeInsets.all(16), - child: Text('Font Size', style: AppTypography.h3), - ), - ...FontSizeOption.values.map((option) => _FontSizeOption( - option: option, - isSelected: currentSize == option, - onTap: () { - ref.read(settingsProvider.notifier).setFontSize(option); - Navigator.pop(sheetContext); - }, - )), - const SizedBox(height: 16), - ], - ), - ), - ); - } - void _handleSignOut() { showDialog( context: context, @@ -578,126 +532,3 @@ class _LanguageOption extends StatelessWidget { } } -/// Font Size Menu Item Widget -class _FontSizeMenuItem extends StatelessWidget { - final FontSizeOption currentSize; - final VoidCallback onTap; - - const _FontSizeMenuItem({ - required this.currentSize, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: onTap, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), - decoration: BoxDecoration( - color: AppColors.surface, - border: Border( - bottom: BorderSide(color: AppColors.gray100), - ), - ), - child: Row( - children: [ - // Icon - Icon( - LucideIcons.type, - size: 22, - color: AppColors.textSecondary, - ), - - const SizedBox(width: 14), - - // Label - Expanded( - child: Text( - 'Font Size', - style: AppTypography.body.copyWith( - fontWeight: FontWeight.w500, - ), - ), - ), - - // Current Value - Text( - currentSize.label, - style: AppTypography.body.copyWith( - color: AppColors.textSecondary, - ), - ), - - const SizedBox(width: 8), - - // Chevron - Icon( - LucideIcons.chevronRight, - size: 20, - color: AppColors.textTertiary, - ), - ], - ), - ), - ); - } -} - -/// Font Size Option Widget -class _FontSizeOption extends StatelessWidget { - final FontSizeOption option; - final bool isSelected; - final VoidCallback onTap; - - const _FontSizeOption({ - required this.option, - required this.isSelected, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: onTap, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), - child: Row( - children: [ - Expanded( - child: Row( - children: [ - Text( - option.label, - style: AppTypography.body.copyWith( - fontWeight: - isSelected ? FontWeight.w600 : FontWeight.normal, - color: isSelected - ? AppColors.primary600 - : AppColors.textPrimary, - ), - ), - const SizedBox(width: 16), - // Preview text - Text( - 'Aa', - style: TextStyle( - fontSize: 14 * option.scale, - color: AppColors.textSecondary, - ), - ), - ], - ), - ), - if (isSelected) - Icon( - LucideIcons.check, - size: 20, - color: AppColors.primary600, - ), - ], - ), - ), - ); - } -} diff --git a/mobile/lib/services/settings_service.dart b/mobile/lib/services/settings_service.dart deleted file mode 100644 index 6b12d3e1..00000000 --- a/mobile/lib/services/settings_service.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -/// Font size options for the app -enum FontSizeOption { - small('Small', 1.0), - defaultSize('Default', 1.25), - large('Large', 1.5); - - const FontSizeOption(this.label, this.scale); - final String label; - final double scale; - - /// Get FontSizeOption from stored string value - static FontSizeOption fromString(String? value) { - switch (value) { - case 'small': - return FontSizeOption.small; - case 'large': - return FontSizeOption.large; - default: - return FontSizeOption.defaultSize; - } - } - - /// Get string value for storage - String toStorageString() { - switch (this) { - case FontSizeOption.small: - return 'small'; - case FontSizeOption.large: - return 'large'; - case FontSizeOption.defaultSize: - return 'default'; - } - } -} - -/// Settings service for persisting app preferences -class SettingsService { - static final SettingsService _instance = SettingsService._internal(); - factory SettingsService() => _instance; - SettingsService._internal(); - - // Storage keys - static const String _fontSizeKey = 'font_size_preference'; - - // State - FontSizeOption _fontSize = FontSizeOption.defaultSize; - - // Getters - FontSizeOption get fontSize => _fontSize; - double get fontScale => _fontSize.scale; - - /// Initialize the settings service - call on app startup - Future initialize() async { - debugPrint('SettingsService: Initializing...'); - await _loadFromStorage(); - debugPrint('SettingsService: Initialized with font size: ${_fontSize.label}'); - } - - /// Set font size preference - Future setFontSize(FontSizeOption size) async { - _fontSize = size; - await _saveToStorage(); - debugPrint('SettingsService: Font size set to ${size.label}'); - } - - /// Load settings from SharedPreferences - Future _loadFromStorage() async { - try { - final prefs = await SharedPreferences.getInstance(); - final fontSizeValue = prefs.getString(_fontSizeKey); - _fontSize = FontSizeOption.fromString(fontSizeValue); - } catch (e) { - debugPrint('SettingsService: Error loading settings: $e'); - _fontSize = FontSizeOption.defaultSize; - } - } - - /// Save settings to SharedPreferences - Future _saveToStorage() async { - try { - final prefs = await SharedPreferences.getInstance(); - await prefs.setString(_fontSizeKey, _fontSize.toStorageString()); - } catch (e) { - debugPrint('SettingsService: Error saving settings: $e'); - } - } -}