feat: LifeFlow - Personal Life Management System - Initial Commit
PROJECT FOUNDATION: - Complete project structure (Android, PC, Backend, HA) - Comprehensive README with vision and features - Detailed development plan (6 week timeline) ANDROID APP - MVP STRUCTURE: - pubspec.yaml with all dependencies (Riverpod, Hive, Flutter) - Data models with Hive annotations: - Routine (with categories, schedules, reminders) - Activity (completion tracking, mood) - Gamification (stats, badges, levels) - Repository layer with Riverpod providers - Beautiful dashboard UI with: - Daily progress ring - Today's routines list - Quick stats (hydration, meds, points) - Streak visualization - Add routine bottom sheet - Complete theme system (Light/Dark) FEATURES PLANNED: - 10 routine categories (meds, vitamins, appointments, sleep, food, hydration, exercise, hygiene, self-care) - Gamification (points, streaks, badges, levels) - SMS parsing for appointments - Home Assistant integration - PC companion app - Cloud sync Status: Ready for development!
This commit is contained in:
commit
545e092d3b
|
|
@ -0,0 +1,252 @@
|
|||
# LifeFlow - Personal Life Management System
|
||||
|
||||
> Transform your daily routines into enjoyable habits
|
||||
>
|
||||
> **Android App** | **PC App** | **Home Assistant Integration** | **SMS Parsing**
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Vision
|
||||
|
||||
LifeFlow makes managing your daily life **effortless, engaging, and rewarding**. From medications to hydration, appointments to self-care - everything organized in one beautiful, intuitive app.
|
||||
|
||||
### Core Principles
|
||||
- **Easy** - Minimal friction to log activities
|
||||
- **Fun** - Gamification and rewards
|
||||
- **Smart** - AI-powered suggestions and SMS parsing
|
||||
- **Connected** - Syncs across all devices + Home Assistant
|
||||
|
||||
---
|
||||
|
||||
## 📱 Features Overview
|
||||
|
||||
### 1. Medication & Vitamins 💊
|
||||
- **Smart Reminders** - Time-based with snooze options
|
||||
- **Dose Tracking** - Log what you took and when
|
||||
- **Refill Alerts** - Warns when running low
|
||||
- **Interaction Checker** - Flags potential conflicts
|
||||
- **History View** - Complete medication timeline
|
||||
|
||||
### 2. Appointments & Meetings 📅
|
||||
- **Calendar Sync** - Google/Outlook integration
|
||||
- **SMS Parsing** - Auto-extract from text messages
|
||||
- **Smart Scheduling** - Suggests optimal times
|
||||
- **Prep Reminders** - Reminds you to prepare
|
||||
- **Travel Time** - Includes commute in notifications
|
||||
|
||||
### 3. Sleep Schedule 😴
|
||||
- **Sleep Tracking** - Duration and quality
|
||||
- **Wind-Down Alerts** - Prepare for bedtime
|
||||
- **Smart Alarm** - Wake at optimal cycle
|
||||
- **Sleep Hygiene** - Tips and reminders
|
||||
- **Trends Analysis** - Weekly/monthly patterns
|
||||
|
||||
### 4. Food & Intake 🍎
|
||||
- **Meal Logging** - Quick photo + notes
|
||||
- **Macro Tracking** - Protein, carbs, fats
|
||||
- **Water Intake** - Hydration reminders
|
||||
- **Calorie Estimation** - Smart suggestions
|
||||
- **Meal Prep Reminders** - Plan ahead
|
||||
|
||||
### 5. Self Care & Hygiene 🧘
|
||||
- **Routine Checklists** - Morning/evening rituals
|
||||
- **Skincare Tracking** - Product usage log
|
||||
- **Exercise Logging** - Workout tracking
|
||||
- **Mental Health** - Mood journaling
|
||||
- **Meditation Timer** - Mindfulness sessions
|
||||
|
||||
### 6. Hydration 💧
|
||||
- **Smart Reminders** - Based on activity level
|
||||
- **Quick Log** - One-tap water logging
|
||||
- **Goal Tracking** - Daily target visualization
|
||||
- **Streak Counter** - Consecutive days hit
|
||||
- **Weather Aware** - More reminders on hot days
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Gamification System
|
||||
|
||||
### Points & Rewards
|
||||
- **Activity Points** - Earn for completing routines
|
||||
- **Streak Bonuses** - Multipliers for consistency
|
||||
- **Achievement Badges** - Unlock milestones
|
||||
- **Level System** - Progress from Novice to Master
|
||||
- **Weekly Challenges** - Special themed goals
|
||||
|
||||
### Visual Progress
|
||||
- **Flame Streaks** - Visual fire for active streaks
|
||||
- **Progress Rings** - Daily completion circles
|
||||
- **Monthly Calendar** - Heat map of activity
|
||||
- **Trend Graphs** - Visualize improvements
|
||||
|
||||
---
|
||||
|
||||
## 🏠 Home Assistant Integration
|
||||
|
||||
### Features
|
||||
- **Dashboard Widget** - Quick status view
|
||||
- **Voice Commands** - "Hey Google, log my vitamins"
|
||||
- **Automations** - Trigger based on routines
|
||||
- Lights dim at bedtime
|
||||
- Coffee maker starts after morning routine
|
||||
- Notifications to family members
|
||||
- **Sensors** - Track completion rates
|
||||
- **Scripts** - Run routines from HA
|
||||
|
||||
### Entities Exposed
|
||||
- `sensor.lifeflow_medication_today`
|
||||
- `sensor.lifeflow_water_intake`
|
||||
- `sensor.lifeflow_sleep_quality`
|
||||
- `binary_sensor.lifeflow_all_routines_done`
|
||||
- `button.lifeflow_log_vitamins`
|
||||
|
||||
---
|
||||
|
||||
## 💻 PC App Features
|
||||
|
||||
### Desktop Companion
|
||||
- **Quick Log Widget** - Desktop overlay
|
||||
- **Keyboard Shortcuts** - Ctrl+Shift+L for water
|
||||
- **System Tray** - Always accessible
|
||||
- **Calendar View** - Weekly planning
|
||||
- **Data Export** - CSV/JSON for analysis
|
||||
|
||||
### Cross-Platform Sync
|
||||
- **Real-time Sync** - Instant across devices
|
||||
- **Offline Mode** - Works without internet
|
||||
- **Conflict Resolution** - Smart merge
|
||||
- **Backup & Restore** - Never lose data
|
||||
|
||||
---
|
||||
|
||||
## 📱 Android App Architecture
|
||||
|
||||
### Tech Stack
|
||||
- **Framework:** Flutter (cross-platform)
|
||||
- **State Management:** Riverpod
|
||||
- **Local DB:** Hive (fast, lightweight)
|
||||
- **Backend:** Firebase / Supabase
|
||||
- **Notifications:** Flutter Local Notifications
|
||||
- **SMS:** Telephony plugin
|
||||
|
||||
### Screens
|
||||
1. **Dashboard** - Today's overview
|
||||
2. **Routines** - All routine categories
|
||||
3. **Calendar** - Schedule view
|
||||
4. **Stats** - Progress & analytics
|
||||
5. **Settings** - Configuration
|
||||
6. **Add Routine** - Create new routines
|
||||
|
||||
---
|
||||
|
||||
## 🤖 SMS Parsing AI
|
||||
|
||||
### Extracts From Texts
|
||||
**Appointment SMS:**
|
||||
```
|
||||
"Your appointment with Dr. Smith is
|
||||
confirmed for March 15 at 2:30 PM at
|
||||
Downtown Medical Center"
|
||||
```
|
||||
→ Auto-adds to calendar with location
|
||||
|
||||
**Medication Reminders:**
|
||||
```
|
||||
"Your prescription is ready for pickup
|
||||
at CVS Pharmacy. Refill #3 of 5."
|
||||
```
|
||||
→ Logs refill, updates inventory
|
||||
|
||||
**Meeting Invites:**
|
||||
```
|
||||
"Team standup tomorrow 9 AM in Conference
|
||||
Room B. Bring your reports."
|
||||
```
|
||||
→ Adds meeting with notes
|
||||
|
||||
---
|
||||
|
||||
## 📊 Data Model
|
||||
|
||||
### Core Entities
|
||||
```
|
||||
User
|
||||
├── Routines[]
|
||||
├── Activities[]
|
||||
├── Medications[]
|
||||
├── Appointments[]
|
||||
├── SleepLogs[]
|
||||
├── FoodLogs[]
|
||||
└── Settings
|
||||
|
||||
Routine
|
||||
├── name: string
|
||||
├── category: enum
|
||||
├── schedule: CronExpression
|
||||
├── reminders: Reminder[]
|
||||
├── points: number
|
||||
└── streak: number
|
||||
|
||||
Activity
|
||||
├── routineId: string
|
||||
├── timestamp: datetime
|
||||
├── completed: boolean
|
||||
├── notes: string
|
||||
└── mood: enum
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Privacy & Security
|
||||
|
||||
- **Local-First** - Data stored on device
|
||||
- **Encrypted Sync** - End-to-end encryption
|
||||
- **No Data Selling** - Your data is yours
|
||||
- **Export Anytime** - Full data portability
|
||||
- **Optional Cloud** - Sync only if you want
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Development Phases
|
||||
|
||||
### Phase 1: Core Android App
|
||||
- Basic routine tracking
|
||||
- Notifications
|
||||
- Local storage
|
||||
- Simple gamification
|
||||
|
||||
### Phase 2: Smart Features
|
||||
- SMS parsing
|
||||
- AI suggestions
|
||||
- Advanced analytics
|
||||
- Widgets
|
||||
|
||||
### Phase 3: Ecosystem
|
||||
- PC app
|
||||
- Home Assistant
|
||||
- Cloud sync
|
||||
- Social features
|
||||
|
||||
---
|
||||
|
||||
## 📈 Success Metrics
|
||||
|
||||
- Daily active users
|
||||
- Routine completion rates
|
||||
- User retention (7/30/90 day)
|
||||
- Average streak length
|
||||
- Feature adoption
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Design Philosophy
|
||||
|
||||
- **Calming Colors** - Blues, greens, soft tones
|
||||
- **Minimalist** - Clean, uncluttered UI
|
||||
- **Accessible** - WCAG compliant
|
||||
- **Delightful** - Micro-interactions, animations
|
||||
- **Personal** - Customizable themes
|
||||
|
||||
---
|
||||
|
||||
Ready to build! 🚀
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'screens/dashboard/dashboard_screen.dart';
|
||||
import 'screens/routines/routines_screen.dart';
|
||||
import 'screens/calendar/calendar_screen.dart';
|
||||
import 'screens/stats/stats_screen.dart';
|
||||
import 'screens/settings/settings_screen.dart';
|
||||
import 'theme/app_theme.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Initialize Hive (local database)
|
||||
// await Hive.initFlutter();
|
||||
// _registerAdapters();
|
||||
|
||||
runApp(
|
||||
const ProviderScope(
|
||||
child: LifeFlowApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class LifeFlowApp extends StatelessWidget {
|
||||
const LifeFlowApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'LifeFlow',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: AppTheme.lightTheme,
|
||||
darkTheme: AppTheme.darkTheme,
|
||||
themeMode: ThemeMode.system,
|
||||
home: const MainNavigationScreen(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MainNavigationScreen extends StatefulWidget {
|
||||
const MainNavigationScreen({super.key});
|
||||
|
||||
@override
|
||||
State<MainNavigationScreen> createState() => _MainNavigationScreenState();
|
||||
}
|
||||
|
||||
class _MainNavigationScreenState extends State<MainNavigationScreen> {
|
||||
int _currentIndex = 0;
|
||||
|
||||
final _screens = const [
|
||||
DashboardScreen(),
|
||||
RoutinesScreen(),
|
||||
CalendarScreen(),
|
||||
StatsScreen(),
|
||||
SettingsScreen(),
|
||||
];
|
||||
|
||||
final _destinations = const [
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.dashboard_outlined),
|
||||
selectedIcon: Icon(Icons.dashboard),
|
||||
label: 'Home',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.checklist_outlined),
|
||||
selectedIcon: Icon(Icons.checklist),
|
||||
label: 'Routines',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.calendar_today_outlined),
|
||||
selectedIcon: Icon(Icons.calendar_today),
|
||||
label: 'Calendar',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.insights_outlined),
|
||||
selectedIcon: Icon(Icons.insights),
|
||||
label: 'Stats',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.settings_outlined),
|
||||
selectedIcon: Icon(Icons.settings),
|
||||
label: 'Settings',
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: _screens[_currentIndex],
|
||||
),
|
||||
bottomNavigationBar: NavigationBar(
|
||||
selectedIndex: _currentIndex,
|
||||
onDestinationSelected: (index) {
|
||||
setState(() {
|
||||
_currentIndex = index;
|
||||
});
|
||||
},
|
||||
destinations: _destinations,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'routine.dart';
|
||||
|
||||
part 'activity.freezed.dart';
|
||||
part 'activity.g.dart';
|
||||
|
||||
@HiveType(typeId: 5)
|
||||
enum Mood {
|
||||
@HiveField(0)
|
||||
terrible,
|
||||
@HiveField(1)
|
||||
bad,
|
||||
@HiveField(2)
|
||||
neutral,
|
||||
@HiveField(3)
|
||||
good,
|
||||
@HiveField(4)
|
||||
great,
|
||||
}
|
||||
|
||||
@HiveType(typeId: 6)
|
||||
@freezed
|
||||
class Activity with _$Activity {
|
||||
const factory Activity({
|
||||
@HiveField(0) required String id,
|
||||
@HiveField(1) required String routineId,
|
||||
@HiveField(2) required DateTime timestamp,
|
||||
@HiveField(3) @Default(true) bool completed,
|
||||
@HiveField(4) String? notes,
|
||||
@HiveField(5) Mood? mood,
|
||||
@HiveField(6) @Default(0) int pointsEarned,
|
||||
@HiveField(7) DateTime? scheduledTime,
|
||||
}) = _Activity;
|
||||
|
||||
factory Activity.fromJson(Map<String, dynamic> json) =
|
||||
_$$ActivityImplFromJson;
|
||||
}
|
||||
|
||||
extension MoodExtension on Mood {
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case Mood.terrible:
|
||||
return 'Terrible';
|
||||
case Mood.bad:
|
||||
return 'Bad';
|
||||
case Mood.neutral:
|
||||
return 'Okay';
|
||||
case Mood.good:
|
||||
return 'Good';
|
||||
case Mood.great:
|
||||
return 'Great!';
|
||||
}
|
||||
}
|
||||
|
||||
String get emoji {
|
||||
switch (this) {
|
||||
case Mood.terrible:
|
||||
return '😫';
|
||||
case Mood.bad:
|
||||
return '😕';
|
||||
case Mood.neutral:
|
||||
return '😐';
|
||||
case Mood.good:
|
||||
return '🙂';
|
||||
case Mood.great:
|
||||
return '😄';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@HiveType(typeId: 7)
|
||||
@freezed
|
||||
class ActivityStats with _$ActivityStats {
|
||||
const factory ActivityStats({
|
||||
@HiveField(0) required int totalCompleted,
|
||||
@HiveField(1) required int totalSkipped,
|
||||
@HiveField(2) required double completionRate,
|
||||
@HiveField(3) required int currentStreak,
|
||||
@HiveField(4) required int longestStreak,
|
||||
@HiveField(5) required int totalPoints,
|
||||
@HiveField(6) @Default({}) Map<String, int> categoryCompletion,
|
||||
}) = _ActivityStats;
|
||||
|
||||
factory ActivityStats.fromJson(Map<String, dynamic> json) =
|
||||
_$$ActivityStatsImplFromJson;
|
||||
}
|
||||
|
||||
// Helper class for daily progress
|
||||
class DailyProgress {
|
||||
final DateTime date;
|
||||
final int totalRoutines;
|
||||
final int completedRoutines;
|
||||
final int totalPoints;
|
||||
final bool allCompleted;
|
||||
|
||||
DailyProgress({
|
||||
required this.date,
|
||||
required this.totalRoutines,
|
||||
required this.completedRoutines,
|
||||
required this.totalPoints,
|
||||
}) : allCompleted = totalRoutines > 0 && totalRoutines == completedRoutines;
|
||||
|
||||
double get completionPercentage =
|
||||
totalRoutines > 0 ? (completedRoutines / totalRoutines) * 100 : 0;
|
||||
}
|
||||
|
||||
// Helper for streak calculation
|
||||
class StreakCalculator {
|
||||
static int calculateCurrentStreak(List<Activity> activities) {
|
||||
if (activities.isEmpty) return 0;
|
||||
|
||||
// Sort by date descending
|
||||
final sorted = activities.toList()
|
||||
..sort((a, b) => b.timestamp.compareTo(a.timestamp));
|
||||
|
||||
int streak = 0;
|
||||
DateTime checkDate = DateTime.now();
|
||||
|
||||
for (final activity in sorted) {
|
||||
final activityDate = DateTime(
|
||||
activity.timestamp.year,
|
||||
activity.timestamp.month,
|
||||
activity.timestamp.day,
|
||||
);
|
||||
|
||||
final checkDateOnly = DateTime(
|
||||
checkDate.year,
|
||||
checkDate.month,
|
||||
checkDate.day,
|
||||
);
|
||||
|
||||
if (activityDate.isAtSameMomentAs(checkDateOnly) && activity.completed) {
|
||||
streak++;
|
||||
checkDate = checkDate.subtract(const Duration(days: 1));
|
||||
} else if (activityDate.isBefore(checkDateOnly)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return streak;
|
||||
}
|
||||
|
||||
static int calculateLongestStreak(List<Activity> activities) {
|
||||
if (activities.isEmpty) return 0;
|
||||
|
||||
// Group by day
|
||||
final dailyCompletion = <DateTime, bool>{};
|
||||
for (final activity in activities) {
|
||||
final date = DateTime(
|
||||
activity.timestamp.year,
|
||||
activity.timestamp.month,
|
||||
activity.timestamp.day,
|
||||
);
|
||||
dailyCompletion[date] = (dailyCompletion[date] ?? true) && activity.completed;
|
||||
}
|
||||
|
||||
// Sort dates
|
||||
final dates = dailyCompletion.keys.toList()..sort();
|
||||
|
||||
int longestStreak = 0;
|
||||
int currentStreak = 0;
|
||||
|
||||
for (int i = 0; i < dates.length; i++) {
|
||||
if (dailyCompletion[dates[i]] == true) {
|
||||
currentStreak++;
|
||||
longestStreak = longestStreak > currentStreak ? longestStreak : currentStreak;
|
||||
} else {
|
||||
currentStreak = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return longestStreak;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,275 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'gamification.freezed.dart';
|
||||
part 'gamification.g.dart';
|
||||
|
||||
@HiveType(typeId: 8)
|
||||
@freezed
|
||||
class GamificationStats with _$GamificationStats {
|
||||
const factory GamificationStats({
|
||||
@HiveField(0) @Default(0) int totalPoints,
|
||||
@HiveField(1) @Default(0) int currentStreak,
|
||||
@HiveField(2) @Default(0) int longestStreak,
|
||||
@HiveField(3) @Default(1) int level,
|
||||
@HiveField(4) @Default([]) List<String> unlockedBadges,
|
||||
@HiveField(5) @Default({}) Map<String, int> categoryStreaks,
|
||||
@HiveField(6) @Default(0) int routinesCompletedToday,
|
||||
@HiveField(7) @Default(0) int totalRoutinesCompleted,
|
||||
@HiveField(8) @Default(0) int perfectDays,
|
||||
@HiveField(9) required DateTime lastUpdated,
|
||||
}) = _GamificationStats;
|
||||
|
||||
factory GamificationStats.fromJson(Map<String, dynamic> json) =
|
||||
_$$GamificationStatsImplFromJson;
|
||||
}
|
||||
|
||||
@HiveType(typeId: 9)
|
||||
class Badge extends HiveObject {
|
||||
@HiveField(0)
|
||||
final String id;
|
||||
|
||||
@HiveField(1)
|
||||
final String name;
|
||||
|
||||
@HiveField(2)
|
||||
final String description;
|
||||
|
||||
@HiveField(3)
|
||||
final String icon;
|
||||
|
||||
@HiveField(4)
|
||||
final int pointsRequired;
|
||||
|
||||
@HiveField(5)
|
||||
final BadgeCategory category;
|
||||
|
||||
Badge({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.icon,
|
||||
required this.pointsRequired,
|
||||
required this.category,
|
||||
});
|
||||
}
|
||||
|
||||
@HiveType(typeId: 10)
|
||||
enum BadgeCategory {
|
||||
@HiveField(0)
|
||||
streak,
|
||||
@HiveField(1)
|
||||
completion,
|
||||
@HiveField(2)
|
||||
category,
|
||||
@HiveField(3)
|
||||
special,
|
||||
}
|
||||
|
||||
// Predefined badges
|
||||
class Badges {
|
||||
static final List<Badge> all = [
|
||||
// Streak badges
|
||||
Badge(
|
||||
id: 'streak_3',
|
||||
name: 'Getting Started',
|
||||
description: '3 day streak',
|
||||
icon: '🔥',
|
||||
pointsRequired: 0,
|
||||
category: BadgeCategory.streak,
|
||||
),
|
||||
Badge(
|
||||
id: 'streak_7',
|
||||
name: 'Week Warrior',
|
||||
description: '7 day streak',
|
||||
icon: '🔥🔥',
|
||||
pointsRequired: 0,
|
||||
category: BadgeCategory.streak,
|
||||
),
|
||||
Badge(
|
||||
id: 'streak_30',
|
||||
name: 'Monthly Master',
|
||||
description: '30 day streak',
|
||||
icon: '📅',
|
||||
pointsRequired: 0,
|
||||
category: BadgeCategory.streak,
|
||||
),
|
||||
Badge(
|
||||
id: 'streak_100',
|
||||
name: 'Centurion',
|
||||
description: '100 day streak',
|
||||
icon: '💯',
|
||||
pointsRequired: 0,
|
||||
category: BadgeCategory.streak,
|
||||
),
|
||||
|
||||
// Completion badges
|
||||
Badge(
|
||||
id: 'first_routine',
|
||||
name: 'First Step',
|
||||
description: 'Complete your first routine',
|
||||
icon: '👟',
|
||||
pointsRequired: 0,
|
||||
category: BadgeCategory.completion,
|
||||
),
|
||||
Badge(
|
||||
id: 'routines_10',
|
||||
name: 'Getting Consistent',
|
||||
description: 'Complete 10 routines',
|
||||
icon: '⭐',
|
||||
pointsRequired: 0,
|
||||
category: BadgeCategory.completion,
|
||||
),
|
||||
Badge(
|
||||
id: 'routines_100',
|
||||
name: 'Century Club',
|
||||
description: 'Complete 100 routines',
|
||||
icon: '🏆',
|
||||
pointsRequired: 0,
|
||||
category: BadgeCategory.completion,
|
||||
),
|
||||
Badge(
|
||||
id: 'perfect_week',
|
||||
name: 'Perfect Week',
|
||||
description: 'Complete all routines for 7 days',
|
||||
icon: '🌟',
|
||||
pointsRequired: 0,
|
||||
category: BadgeCategory.completion,
|
||||
),
|
||||
|
||||
// Category badges
|
||||
Badge(
|
||||
id: 'hydration_master',
|
||||
name: 'Hydration Hero',
|
||||
description: 'Hit water goal 7 days in a row',
|
||||
icon: '💧',
|
||||
pointsRequired: 0,
|
||||
category: BadgeCategory.category,
|
||||
),
|
||||
Badge(
|
||||
id: 'medication_reliable',
|
||||
name: 'Reliable',
|
||||
description: 'Take medication on time for 14 days',
|
||||
icon: '💊',
|
||||
pointsRequired: 0,
|
||||
category: BadgeCategory.category,
|
||||
),
|
||||
Badge(
|
||||
id: 'sleep_schedule',
|
||||
name: 'Sleep Master',
|
||||
description: 'Maintain sleep schedule for 7 days',
|
||||
icon: '😴',
|
||||
pointsRequired: 0,
|
||||
category: BadgeCategory.category,
|
||||
),
|
||||
|
||||
// Special badges
|
||||
Badge(
|
||||
id: 'early_bird',
|
||||
name: 'Early Bird',
|
||||
description: 'Complete morning routine before 8 AM',
|
||||
icon: '🐦',
|
||||
pointsRequired: 0,
|
||||
category: BadgeCategory.special,
|
||||
),
|
||||
Badge(
|
||||
id: 'night_owl',
|
||||
name: 'Night Owl',
|
||||
description: 'Complete evening routine after 9 PM',
|
||||
icon: '🦉',
|
||||
pointsRequired: 0,
|
||||
category: BadgeCategory.special,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
// Level calculation
|
||||
class LevelCalculator {
|
||||
static int calculateLevel(int totalPoints) {
|
||||
// Exponential level curve
|
||||
// Level 1: 0 points
|
||||
// Level 2: 100 points
|
||||
// Level 3: 250 points
|
||||
// Level 4: 450 points
|
||||
// etc.
|
||||
|
||||
if (totalPoints < 100) return 1;
|
||||
if (totalPoints < 250) return 2;
|
||||
if (totalPoints < 450) return 3;
|
||||
if (totalPoints < 700) return 4;
|
||||
if (totalPoints < 1000) return 5;
|
||||
|
||||
// Beyond level 5: every 500 points
|
||||
return 5 + ((totalPoints - 1000) ~/ 500);
|
||||
}
|
||||
|
||||
static int pointsToNextLevel(int currentLevel, int totalPoints) {
|
||||
final nextLevelThreshold = _levelThreshold(currentLevel + 1);
|
||||
return nextLevelThreshold - totalPoints;
|
||||
}
|
||||
|
||||
static double progressToNextLevel(int currentLevel, int totalPoints) {
|
||||
final currentThreshold = _levelThreshold(currentLevel);
|
||||
final nextThreshold = _levelThreshold(currentLevel + 1);
|
||||
final progress = totalPoints - currentThreshold;
|
||||
final needed = nextThreshold - currentThreshold;
|
||||
return (progress / needed).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
static int _levelThreshold(int level) {
|
||||
switch (level) {
|
||||
case 1:
|
||||
return 0;
|
||||
case 2:
|
||||
return 100;
|
||||
case 3:
|
||||
return 250;
|
||||
case 4:
|
||||
return 450;
|
||||
case 5:
|
||||
return 700;
|
||||
default:
|
||||
return 1000 + ((level - 6) * 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BadgeChecker {
|
||||
static List<Badge> checkUnlockedBadges(
|
||||
GamificationStats stats,
|
||||
List<String> alreadyUnlocked,
|
||||
) {
|
||||
final newlyUnlocked = <Badge>[];
|
||||
|
||||
for (final badge in Badges.all) {
|
||||
if (alreadyUnlocked.contains(badge.id)) continue;
|
||||
|
||||
if (_meetsCriteria(badge, stats)) {
|
||||
newlyUnlocked.add(badge);
|
||||
}
|
||||
}
|
||||
|
||||
return newlyUnlocked;
|
||||
}
|
||||
|
||||
static bool _meetsCriteria(Badge badge, GamificationStats stats) {
|
||||
switch (badge.id) {
|
||||
case 'streak_3':
|
||||
return stats.currentStreak >= 3;
|
||||
case 'streak_7':
|
||||
return stats.currentStreak >= 7;
|
||||
case 'streak_30':
|
||||
return stats.currentStreak >= 30;
|
||||
case 'streak_100':
|
||||
return stats.currentStreak >= 100;
|
||||
case 'routines_10':
|
||||
return stats.totalRoutinesCompleted >= 10;
|
||||
case 'routines_100':
|
||||
return stats.totalRoutinesCompleted >= 100;
|
||||
case 'perfect_week':
|
||||
return stats.perfectDays >= 7;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'routine.freezed.dart';
|
||||
part 'routine.g.dart';
|
||||
|
||||
@HiveType(typeId: 0)
|
||||
enum RoutineCategory {
|
||||
@HiveField(0)
|
||||
medication,
|
||||
@HiveField(1)
|
||||
vitamin,
|
||||
@HiveField(2)
|
||||
appointment,
|
||||
@HiveField(3)
|
||||
sleep,
|
||||
@HiveField(4)
|
||||
food,
|
||||
@HiveField(5)
|
||||
hydration,
|
||||
@HiveField(6)
|
||||
exercise,
|
||||
@HiveField(7)
|
||||
hygiene,
|
||||
@HiveField(8)
|
||||
selfCare,
|
||||
@HiveField(9)
|
||||
custom,
|
||||
}
|
||||
|
||||
@HiveType(typeId: 1)
|
||||
@freezed
|
||||
class Routine with _$Routine {
|
||||
const factory Routine({
|
||||
@HiveField(0) required String id,
|
||||
@HiveField(1) required String name,
|
||||
@HiveField(2) String? description,
|
||||
@HiveField(3) required RoutineCategory category,
|
||||
@HiveField(4) required Schedule schedule,
|
||||
@HiveField(5) @Default([]) List<Reminder> reminders,
|
||||
@HiveField(6) @Default(10) int points,
|
||||
@HiveField(7) String? icon,
|
||||
@HiveField(8) String? color,
|
||||
@HiveField(9) @Default(true) bool isActive,
|
||||
@HiveField(10) required DateTime createdAt,
|
||||
@HiveField(11) DateTime? updatedAt,
|
||||
}) = _Routine;
|
||||
|
||||
factory Routine.fromJson(Map<String, dynamic> json) =
|
||||
_$$RoutineImplFromJson;
|
||||
}
|
||||
|
||||
@HiveType(typeId: 2)
|
||||
@freezed
|
||||
class Schedule with _$Schedule {
|
||||
const factory Schedule({
|
||||
@HiveField(0) required ScheduleType type,
|
||||
@HiveField(1) List<int>? daysOfWeek, // 0 = Monday, 6 = Sunday
|
||||
@HiveField(2) DateTime? specificDate,
|
||||
@HiveField(3) required String time, // "HH:mm" format
|
||||
@HiveField(4) int? intervalDays, // For every X days
|
||||
}) = _Schedule;
|
||||
|
||||
factory Schedule.fromJson(Map<String, dynamic> json) =
|
||||
_$$ScheduleImplFromJson;
|
||||
}
|
||||
|
||||
@HiveType(typeId: 3)
|
||||
enum ScheduleType {
|
||||
@HiveField(0)
|
||||
daily,
|
||||
@HiveField(1)
|
||||
weekly,
|
||||
@HiveField(2)
|
||||
specificDate,
|
||||
@HiveField(3)
|
||||
interval,
|
||||
}
|
||||
|
||||
@HiveType(typeId: 4)
|
||||
@freezed
|
||||
class Reminder with _$Reminder {
|
||||
const factory Reminder({
|
||||
@HiveField(0) required String id,
|
||||
@HiveField(1) required int minutesBefore,
|
||||
@HiveField(2) @Default(true) bool isEnabled,
|
||||
}) = _Reminder;
|
||||
|
||||
factory Reminder.fromJson(Map<String, dynamic> json) =
|
||||
_$$ReminderImplFromJson;
|
||||
}
|
||||
|
||||
// Extension for category metadata
|
||||
extension RoutineCategoryExtension on RoutineCategory {
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case RoutineCategory.medication:
|
||||
return 'Medication';
|
||||
case RoutineCategory.vitamin:
|
||||
return 'Vitamins';
|
||||
case RoutineCategory.appointment:
|
||||
return 'Appointments';
|
||||
case RoutineCategory.sleep:
|
||||
return 'Sleep';
|
||||
case RoutineCategory.food:
|
||||
return 'Food & Nutrition';
|
||||
case RoutineCategory.hydration:
|
||||
return 'Hydration';
|
||||
case RoutineCategory.exercise:
|
||||
return 'Exercise';
|
||||
case RoutineCategory.hygiene:
|
||||
return 'Hygiene';
|
||||
case RoutineCategory.selfCare:
|
||||
return 'Self Care';
|
||||
case RoutineCategory.custom:
|
||||
return 'Custom';
|
||||
}
|
||||
}
|
||||
|
||||
String get icon {
|
||||
switch (this) {
|
||||
case RoutineCategory.medication:
|
||||
return '💊';
|
||||
case RoutineCategory.vitamin:
|
||||
return '🍊';
|
||||
case RoutineCategory.appointment:
|
||||
return '📅';
|
||||
case RoutineCategory.sleep:
|
||||
return '😴';
|
||||
case RoutineCategory.food:
|
||||
return '🍎';
|
||||
case RoutineCategory.hydration:
|
||||
return '💧';
|
||||
case RoutineCategory.exercise:
|
||||
return '🏃';
|
||||
case RoutineCategory.hygiene:
|
||||
return '🧼';
|
||||
case RoutineCategory.selfCare:
|
||||
return '🧘';
|
||||
case RoutineCategory.custom:
|
||||
return '✨';
|
||||
}
|
||||
}
|
||||
|
||||
int get defaultPoints {
|
||||
switch (this) {
|
||||
case RoutineCategory.medication:
|
||||
case RoutineCategory.vitamin:
|
||||
return 20;
|
||||
case RoutineCategory.appointment:
|
||||
return 15;
|
||||
case RoutineCategory.exercise:
|
||||
return 25;
|
||||
case RoutineCategory.sleep:
|
||||
return 30;
|
||||
case RoutineCategory.hydration:
|
||||
return 10;
|
||||
default:
|
||||
return 15;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import '../models/routine.dart';
|
||||
|
||||
final routineRepositoryProvider = Provider<RoutineRepository>((ref) {
|
||||
return RoutineRepository();
|
||||
});
|
||||
|
||||
final routinesProvider = StreamProvider<List<Routine>>((ref) {
|
||||
final repository = ref.watch(routineRepositoryProvider);
|
||||
return repository.watchAllRoutines();
|
||||
});
|
||||
|
||||
final todayRoutinesProvider = FutureProvider<List<Routine>>((ref) async {
|
||||
final repository = ref.watch(routineRepositoryProvider);
|
||||
return repository.getTodaysRoutines();
|
||||
});
|
||||
|
||||
class RoutineRepository {
|
||||
static const String _boxName = 'routines';
|
||||
Box<Routine>? _box;
|
||||
|
||||
Future<Box<Routine>> _getBox() async {
|
||||
_box ??= await Hive.openBox<Routine>(_boxName);
|
||||
return _box!;
|
||||
}
|
||||
|
||||
// Create
|
||||
Future<void> createRoutine(Routine routine) async {
|
||||
final box = await _getBox();
|
||||
await box.put(routine.id, routine);
|
||||
}
|
||||
|
||||
// Read
|
||||
Future<Routine?> getRoutine(String id) async {
|
||||
final box = await _getBox();
|
||||
return box.get(id);
|
||||
}
|
||||
|
||||
Future<List<Routine>> getAllRoutines() async {
|
||||
final box = await _getBox();
|
||||
return box.values.toList();
|
||||
}
|
||||
|
||||
Stream<List<Routine>> watchAllRoutines() async* {
|
||||
final box = await _getBox();
|
||||
yield box.values.toList();
|
||||
yield* box.watch().map((_) => box.values.toList());
|
||||
}
|
||||
|
||||
Future<List<Routine>> getRoutinesByCategory(RoutineCategory category) async {
|
||||
final routines = await getAllRoutines();
|
||||
return routines.where((r) => r.category == category).toList();
|
||||
}
|
||||
|
||||
Future<List<Routine>> getTodaysRoutines() async {
|
||||
final routines = await getAllRoutines();
|
||||
final now = DateTime.now();
|
||||
final weekday = now.weekday - 1; // 0 = Monday, 6 = Sunday
|
||||
|
||||
return routines.where((routine) {
|
||||
if (!routine.isActive) return false;
|
||||
|
||||
switch (routine.schedule.type) {
|
||||
case ScheduleType.daily:
|
||||
return true;
|
||||
case ScheduleType.weekly:
|
||||
return routine.schedule.daysOfWeek?.contains(weekday) ?? false;
|
||||
case ScheduleType.specificDate:
|
||||
final specificDate = routine.schedule.specificDate;
|
||||
if (specificDate == null) return false;
|
||||
return specificDate.year == now.year &&
|
||||
specificDate.month == now.month &&
|
||||
specificDate.day == now.day;
|
||||
case ScheduleType.interval:
|
||||
// Simplified: check if today falls on interval
|
||||
final createdDate = DateTime(
|
||||
routine.createdAt.year,
|
||||
routine.createdAt.month,
|
||||
routine.createdAt.day,
|
||||
);
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final daysDiff = today.difference(createdDate).inDays;
|
||||
final interval = routine.schedule.intervalDays ?? 1;
|
||||
return daysDiff % interval == 0;
|
||||
}
|
||||
}).toList();
|
||||
}
|
||||
|
||||
// Update
|
||||
Future<void> updateRoutine(Routine routine) async {
|
||||
final box = await _getBox();
|
||||
await box.put(routine.id, routine.copyWith(updatedAt: DateTime.now()));
|
||||
}
|
||||
|
||||
Future<void> toggleRoutineActive(String id) async {
|
||||
final routine = await getRoutine(id);
|
||||
if (routine != null) {
|
||||
await updateRoutine(routine.copyWith(isActive: !routine.isActive));
|
||||
}
|
||||
}
|
||||
|
||||
// Delete
|
||||
Future<void> deleteRoutine(String id) async {
|
||||
final box = await _getBox();
|
||||
await box.delete(id);
|
||||
}
|
||||
|
||||
// Statistics
|
||||
Future<Map<RoutineCategory, int>> getRoutineCountsByCategory() async {
|
||||
final routines = await getAllRoutines();
|
||||
final counts = <RoutineCategory, int>{};
|
||||
|
||||
for (final routine in routines) {
|
||||
counts[routine.category] = (counts[routine.category] ?? 0) + 1;
|
||||
}
|
||||
|
||||
return counts;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,581 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../models/routine.dart';
|
||||
import '../../repositories/routine_repository.dart';
|
||||
import '../../theme/app_theme.dart';
|
||||
import '../routines/widgets/routine_card.dart';
|
||||
|
||||
class DashboardScreen extends ConsumerWidget {
|
||||
const DashboardScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final todayRoutinesAsync = ref.watch(todayRoutinesProvider);
|
||||
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
// App Bar with greeting
|
||||
SliverAppBar(
|
||||
expandedHeight: 120,
|
||||
floating: true,
|
||||
pinned: true,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
title: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_getGreeting(),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'Ready to flow?',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.notifications_outlined),
|
||||
onPressed: () {
|
||||
// Show notifications
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.person_outline),
|
||||
onPressed: () {
|
||||
// Show profile
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Daily Progress Ring
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: _buildDailyProgressCard(context),
|
||||
),
|
||||
),
|
||||
|
||||
// Quick Stats
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: _buildQuickStatsRow(),
|
||||
),
|
||||
),
|
||||
|
||||
// Today's Routines Header
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
"Today's Routines",
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// Navigate to all routines
|
||||
},
|
||||
child: const Text('See All'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Today's Routines List
|
||||
todayRoutinesAsync.when(
|
||||
data: (routines) {
|
||||
if (routines.isEmpty) {
|
||||
return const SliverFillRemaining(
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.spa_outlined,
|
||||
size: 64,
|
||||
color: Colors.grey,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'No routines for today',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'Add your first routine to get started!',
|
||||
style: TextStyle(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
final routine = routines[index];
|
||||
return RoutineCard(
|
||||
routine: routine,
|
||||
onTap: () => _showRoutineDetails(context, routine),
|
||||
onComplete: () => _completeRoutine(context, ref, routine),
|
||||
);
|
||||
},
|
||||
childCount: routines.length,
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const SliverFillRemaining(
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error: (error, stack) => SliverFillRemaining(
|
||||
child: Center(
|
||||
child: Text('Error: $error'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Bottom padding
|
||||
const SliverPadding(
|
||||
padding: EdgeInsets.only(bottom: 100),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () => _showAddRoutineDialog(context),
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Add Routine'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getGreeting() {
|
||||
final hour = DateTime.now().hour;
|
||||
if (hour < 12) return 'Good morning';
|
||||
if (hour < 17) return 'Good afternoon';
|
||||
return 'Good evening';
|
||||
}
|
||||
|
||||
Widget _buildDailyProgressCard(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Row(
|
||||
children: [
|
||||
// Progress Ring
|
||||
SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
value: 0.65, // TODO: Calculate from actual data
|
||||
strokeWidth: 10,
|
||||
backgroundColor: Colors.grey[200],
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'65%',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Done',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
// Stats
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Today's Progress",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'7 of 11 routines completed',
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
_buildStatChip(
|
||||
icon: Icons.local_fire_department,
|
||||
label: '5 day streak',
|
||||
color: Colors.orange,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatChip({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required Color color,
|
||||
}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 16, color: color),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuickStatsRow() {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildQuickStatCard(
|
||||
icon: Icons.water_drop,
|
||||
label: 'Hydration',
|
||||
value: '1.5L',
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildQuickStatCard(
|
||||
icon: Icons.medication,
|
||||
label: 'Meds Taken',
|
||||
value: '2/3',
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildQuickStatCard(
|
||||
icon: Icons.star,
|
||||
label: 'Points',
|
||||
value: '245',
|
||||
color: Colors.amber,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuickStatCard({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required String value,
|
||||
required Color color,
|
||||
}) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, color: color, size: 28),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showRoutineDetails(BuildContext context, Routine routine) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
builder: (context) => DraggableScrollableSheet(
|
||||
initialChildSize: 0.6,
|
||||
maxChildSize: 0.9,
|
||||
minChildSize: 0.4,
|
||||
expand: false,
|
||||
builder: (context, scrollController) {
|
||||
return SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
routine.category.icon,
|
||||
style: const TextStyle(fontSize: 40),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
routine.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
routine.category.displayName,
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
if (routine.description != null) ...[
|
||||
Text(
|
||||
routine.description!,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
_buildDetailRow(
|
||||
Icons.schedule,
|
||||
'Scheduled for',
|
||||
routine.schedule.time,
|
||||
),
|
||||
_buildDetailRow(
|
||||
Icons.repeat,
|
||||
'Frequency',
|
||||
_getFrequencyText(routine.schedule),
|
||||
),
|
||||
_buildDetailRow(
|
||||
Icons.star,
|
||||
'Points',
|
||||
'${routine.points} points',
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
// Mark as complete
|
||||
},
|
||||
icon: const Icon(Icons.check_circle),
|
||||
label: const Text('Mark as Complete'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailRow(IconData icon, String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, color: Colors.grey),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getFrequencyText(Schedule schedule) {
|
||||
switch (schedule.type) {
|
||||
case ScheduleType.daily:
|
||||
return 'Every day';
|
||||
case ScheduleType.weekly:
|
||||
final days = schedule.daysOfWeek?.map((d) {
|
||||
final dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||
return dayNames[d];
|
||||
}).join(', ');
|
||||
return days ?? 'Weekly';
|
||||
case ScheduleType.specificDate:
|
||||
return 'One-time';
|
||||
case ScheduleType.interval:
|
||||
return 'Every ${schedule.intervalDays} days';
|
||||
}
|
||||
}
|
||||
|
||||
void _completeRoutine(BuildContext context, WidgetRef ref, Routine routine) {
|
||||
// TODO: Implement completion logic
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('${routine.name} completed! +${routine.points} points'),
|
||||
backgroundColor: Colors.green,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
action: SnackBarAction(
|
||||
label: 'UNDO',
|
||||
onPressed: () {
|
||||
// Undo completion
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showAddRoutineDialog(BuildContext context) {
|
||||
// TODO: Navigate to add routine screen
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => const AddRoutineSheet(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddRoutineSheet extends StatelessWidget {
|
||||
const AddRoutineSheet({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Add New Routine',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
...RoutineCategory.values.map((category) => ListTile(
|
||||
leading: Text(category.icon, style: const TextStyle(fontSize: 28)),
|
||||
title: Text(category.displayName),
|
||||
onTap: () {
|
||||
// Navigate to routine creation with this category
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppTheme {
|
||||
// Primary Colors
|
||||
static const Color primary = Color(0xFF4A90E2);
|
||||
static const Color primaryLight = Color(0xFF7BB3F0);
|
||||
static const Color primaryDark = Color(0xFF2E5C8A);
|
||||
|
||||
// Category Colors
|
||||
static const Color medication = Color(0xFFE74C3C);
|
||||
static const Color vitamin = Color(0xFFF39C12);
|
||||
static const Color appointment = Color(0xFF9B59B6);
|
||||
static const Color sleep = Color(0xFF8E44AD);
|
||||
static const Color food = Color(0xFFE67E22);
|
||||
static const Color hydration = Color(0xFF3498DB);
|
||||
static const Color exercise = Color(0xFF27AE60);
|
||||
static const Color hygiene = Color(0xFF1ABC9C);
|
||||
static const Color selfCare = Color(0xFF16A085);
|
||||
|
||||
// Gamification Colors
|
||||
static const Color gold = Color(0xFFFFD700);
|
||||
static const Color silver = Color(0xFFC0C0C0);
|
||||
static const Color bronze = Color(0xFFCD7F32);
|
||||
|
||||
// Semantic Colors
|
||||
static const Color success = Color(0xFF27AE60);
|
||||
static const Color warning = Color(0xFFF39C12);
|
||||
static const Color error = Color(0xFFE74C3C);
|
||||
static const Color info = Color(0xFF3498DB);
|
||||
|
||||
static Color getCategoryColor(RoutineCategory category) {
|
||||
switch (category) {
|
||||
case RoutineCategory.medication:
|
||||
return medication;
|
||||
case RoutineCategory.vitamin:
|
||||
return vitamin;
|
||||
case RoutineCategory.appointment:
|
||||
return appointment;
|
||||
case RoutineCategory.sleep:
|
||||
return sleep;
|
||||
case RoutineCategory.food:
|
||||
return food;
|
||||
case RoutineCategory.hydration:
|
||||
return hydration;
|
||||
case RoutineCategory.exercise:
|
||||
return exercise;
|
||||
case RoutineCategory.hygiene:
|
||||
return hygiene;
|
||||
case RoutineCategory.selfCare:
|
||||
return selfCare;
|
||||
case RoutineCategory.custom:
|
||||
return primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary enum for compilation
|
||||
enum RoutineCategory {
|
||||
medication,
|
||||
vitamin,
|
||||
appointment,
|
||||
sleep,
|
||||
food,
|
||||
hydration,
|
||||
exercise,
|
||||
hygiene,
|
||||
selfCare,
|
||||
custom,
|
||||
}
|
||||
|
||||
class AppTheme {
|
||||
static ThemeData get lightTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: AppColors.primary,
|
||||
secondary: AppColors.primaryLight,
|
||||
surface: Colors.white,
|
||||
background: Color(0xFFF8F9FA),
|
||||
onPrimary: Colors.white,
|
||||
onSecondary: Colors.white,
|
||||
onSurface: Color(0xFF1A1A2E),
|
||||
onBackground: Color(0xFF1A1A2E),
|
||||
),
|
||||
scaffoldBackgroundColor: const Color(0xFFF8F9FA),
|
||||
appBarTheme: const AppBarTheme(
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Color(0xFF1A1A2E),
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF1A1A2E),
|
||||
),
|
||||
),
|
||||
cardTheme: CardTheme(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||
backgroundColor: Colors.white,
|
||||
selectedItemColor: AppColors.primary,
|
||||
unselectedItemColor: Colors.grey,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
elevation: 8,
|
||||
),
|
||||
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[100],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: AppColors.primary, width: 2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static ThemeData get darkTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: const ColorScheme.dark(
|
||||
primary: AppColors.primaryLight,
|
||||
secondary: AppColors.primary,
|
||||
surface: Color(0xFF1E1E2E),
|
||||
background: Color(0xFF0F0F1A),
|
||||
onPrimary: Colors.white,
|
||||
onSecondary: Colors.white,
|
||||
onSurface: Colors.white,
|
||||
onBackground: Colors.white,
|
||||
),
|
||||
scaffoldBackgroundColor: const Color(0xFF0F0F1A),
|
||||
appBarTheme: const AppBarTheme(
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
cardTheme: CardTheme(
|
||||
elevation: 2,
|
||||
color: const Color(0xFF1E1E2E),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||
backgroundColor: Color(0xFF1E1E2E),
|
||||
selectedItemColor: AppColors.primaryLight,
|
||||
unselectedItemColor: Colors.grey,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
elevation: 8,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
name: lifeflow
|
||||
description: Transform your daily routines into enjoyable habits
|
||||
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
# State Management
|
||||
flutter_riverpod: ^2.4.0
|
||||
riverpod_annotation: ^2.2.0
|
||||
|
||||
# Local Database
|
||||
hive: ^2.2.3
|
||||
hive_flutter: ^1.1.0
|
||||
|
||||
# Notifications
|
||||
flutter_local_notifications: ^16.1.0
|
||||
timezone: ^0.9.2
|
||||
|
||||
# UI Components
|
||||
cupertino_icons: ^1.0.6
|
||||
flutter_svg: ^2.0.9
|
||||
shimmer: ^3.0.0
|
||||
flutter_slidable: ^3.0.1
|
||||
fl_chart: ^0.65.0
|
||||
percent_indicator: ^4.2.3
|
||||
confetti: ^0.7.0
|
||||
|
||||
# Date/Time
|
||||
intl: ^0.18.1
|
||||
jiffy: ^6.2.1
|
||||
|
||||
# Utilities
|
||||
uuid: ^4.2.1
|
||||
equatable: ^2.0.5
|
||||
freezed_annotation: ^2.4.1
|
||||
json_annotation: ^4.8.1
|
||||
|
||||
# SMS (Android only)
|
||||
telephony: ^0.2.0
|
||||
permission_handler: ^11.0.1
|
||||
|
||||
# Backend (Phase 2)
|
||||
# firebase_core: ^2.21.0
|
||||
# firebase_auth: ^4.12.1
|
||||
# cloud_firestore: ^4.12.2
|
||||
# firebase_messaging: ^14.7.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.1
|
||||
|
||||
# Code Generation
|
||||
build_runner: ^2.4.7
|
||||
freezed: ^2.4.5
|
||||
json_serializable: ^6.7.1
|
||||
riverpod_generator: ^2.3.5
|
||||
hive_generator: ^2.0.1
|
||||
|
||||
# Testing
|
||||
mockito: ^5.4.4
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
||||
assets:
|
||||
- assets/icons/
|
||||
- assets/images/
|
||||
- assets/animations/
|
||||
|
||||
fonts:
|
||||
- family: Inter
|
||||
fonts:
|
||||
- asset: assets/fonts/Inter-Regular.ttf
|
||||
- asset: assets/fonts/Inter-Medium.ttf
|
||||
weight: 500
|
||||
- asset: assets/fonts/Inter-SemiBold.ttf
|
||||
weight: 600
|
||||
- asset: assets/fonts/Inter-Bold.ttf
|
||||
weight: 700
|
||||
|
|
@ -0,0 +1,444 @@
|
|||
# LifeFlow Development Plan
|
||||
|
||||
**Project:** LifeFlow - Personal Life Management System
|
||||
**Start Date:** 2026-02-14
|
||||
**Target:** MVP in 2 weeks, Full App in 6 weeks
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ LifeFlow System │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Android App │ PC App │ Web Dashboard │ HA Add-on │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Shared Backend (Firebase/Supabase) │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ AI Services │ SMS Parser │ Notifications │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Android MVP (Week 1-2)
|
||||
|
||||
### Week 1: Foundation
|
||||
**Day 1-2: Project Setup**
|
||||
- [ ] Flutter project initialization
|
||||
- [ ] Architecture setup (Riverpod + Hive)
|
||||
- [ ] Theme system (Dark/Light/Calm)
|
||||
- [ ] Navigation structure
|
||||
|
||||
**Day 3-4: Core Data Models**
|
||||
- [ ] User model
|
||||
- [ ] Routine model
|
||||
- [ ] Activity log model
|
||||
- [ ] Local database (Hive)
|
||||
|
||||
**Day 5-7: Basic UI Screens**
|
||||
- [ ] Dashboard screen
|
||||
- [ ] Routine list screen
|
||||
- [ ] Add/Edit routine screen
|
||||
- [ ] Simple settings
|
||||
|
||||
### Week 2: Features + Polish
|
||||
**Day 8-10: Core Features**
|
||||
- [ ] Routine CRUD operations
|
||||
- [ ] Local notifications
|
||||
- [ ] Basic streak tracking
|
||||
- [ ] Activity logging
|
||||
|
||||
**Day 11-12: Gamification MVP**
|
||||
- [ ] Points system
|
||||
- [ ] Basic badges
|
||||
- [ ] Streak visualization
|
||||
- [ ] Daily progress ring
|
||||
|
||||
**Day 13-14: Polish + Testing**
|
||||
- [ ] Animations
|
||||
- [ ] Error handling
|
||||
- [ ] Onboarding flow
|
||||
- [ ] Beta build
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Smart Features (Week 3-4)
|
||||
|
||||
### Week 3: Intelligence
|
||||
**SMS Parsing**
|
||||
- [ ] SMS permission handling
|
||||
- [ ] Pattern matching engine
|
||||
- [ ] Appointment extraction
|
||||
- [ ] Medication detection
|
||||
- [ ] Manual confirmation UI
|
||||
|
||||
**AI Suggestions**
|
||||
- [ ] Routine recommendation engine
|
||||
- [ ] Optimal timing suggestions
|
||||
- [ ] Pattern recognition
|
||||
- [ ] Smart reminders
|
||||
|
||||
### Week 4: Integration
|
||||
**Backend Setup**
|
||||
- [ ] Firebase/Supabase project
|
||||
- [ ] Authentication
|
||||
- [ ] Cloud sync
|
||||
- [ ] Conflict resolution
|
||||
|
||||
**Widgets**
|
||||
- [ ] Home screen widget
|
||||
- [ ] Quick actions
|
||||
- [ ] Lock screen integration
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Ecosystem (Week 5-6)
|
||||
|
||||
### Week 5: PC App
|
||||
- [ ] Flutter desktop build
|
||||
- [ ] System tray integration
|
||||
- [ ] Global hotkeys
|
||||
- [ ] Quick log overlay
|
||||
- [ ] Data sync
|
||||
|
||||
### Week 6: Home Assistant
|
||||
- [ ] Custom component
|
||||
- [ ] MQTT integration
|
||||
- [ ] Sensors + Buttons
|
||||
- [ ] Automations examples
|
||||
- [ ] Dashboard cards
|
||||
|
||||
---
|
||||
|
||||
## Technical Stack
|
||||
|
||||
### Android App
|
||||
| Layer | Technology |
|
||||
|-------|-----------|
|
||||
| Framework | Flutter 3.x |
|
||||
| State | Riverpod |
|
||||
| Local DB | Hive |
|
||||
| Remote DB | Firebase/Firestore |
|
||||
| Auth | Firebase Auth |
|
||||
| Notifications | flutter_local_notifications |
|
||||
| SMS | telephony |
|
||||
| Analytics | Firebase Analytics |
|
||||
|
||||
### Backend
|
||||
| Service | Technology |
|
||||
|---------|-----------|
|
||||
| Database | Cloud Firestore |
|
||||
| Auth | Firebase Auth |
|
||||
| Functions | Cloud Functions (Node.js) |
|
||||
| Storage | Firebase Storage |
|
||||
| ML | Firebase ML Kit |
|
||||
|
||||
### PC App
|
||||
| Feature | Implementation |
|
||||
|---------|---------------|
|
||||
| Framework | Flutter Desktop |
|
||||
| System Tray | system_tray |
|
||||
| Hotkeys | hotkey_manager |
|
||||
| Notifications | local_notifier |
|
||||
|
||||
### Home Assistant
|
||||
| Component | Type |
|
||||
|-----------|------|
|
||||
| Main Integration | Custom Component |
|
||||
| Communication | MQTT / REST API |
|
||||
| Entities | Sensors, Buttons, Switches |
|
||||
|
||||
---
|
||||
|
||||
## Data Models
|
||||
|
||||
### User
|
||||
```dart
|
||||
class User {
|
||||
String id;
|
||||
String email;
|
||||
String name;
|
||||
String? avatarUrl;
|
||||
UserSettings settings;
|
||||
GamificationStats stats;
|
||||
DateTime createdAt;
|
||||
}
|
||||
```
|
||||
|
||||
### Routine
|
||||
```dart
|
||||
class Routine {
|
||||
String id;
|
||||
String name;
|
||||
String? description;
|
||||
RoutineCategory category;
|
||||
Schedule schedule;
|
||||
List<Reminder> reminders;
|
||||
int points;
|
||||
String? icon;
|
||||
String? color;
|
||||
bool isActive;
|
||||
DateTime createdAt;
|
||||
}
|
||||
|
||||
enum RoutineCategory {
|
||||
medication,
|
||||
vitamin,
|
||||
appointment,
|
||||
sleep,
|
||||
food,
|
||||
hydration,
|
||||
exercise,
|
||||
hygiene,
|
||||
selfCare,
|
||||
custom
|
||||
}
|
||||
```
|
||||
|
||||
### Activity
|
||||
```dart
|
||||
class Activity {
|
||||
String id;
|
||||
String routineId;
|
||||
DateTime timestamp;
|
||||
bool completed;
|
||||
String? notes;
|
||||
Mood? mood;
|
||||
int pointsEarned;
|
||||
}
|
||||
```
|
||||
|
||||
### Gamification
|
||||
```dart
|
||||
class GamificationStats {
|
||||
int totalPoints;
|
||||
int currentStreak;
|
||||
int longestStreak;
|
||||
int level;
|
||||
List<String> unlockedBadges;
|
||||
Map<String, int> categoryStreaks;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UI/UX Design System
|
||||
|
||||
### Colors
|
||||
```dart
|
||||
class AppColors {
|
||||
// Primary
|
||||
static const primary = Color(0xFF4A90E2);
|
||||
static const primaryLight = Color(0xFF7BB3F0);
|
||||
static const primaryDark = Color(0xFF2E5C8A);
|
||||
|
||||
// Categories
|
||||
static const medication = Color(0xFFE74C3C);
|
||||
static const vitamin = Color(0xFFF39C12);
|
||||
static const hydration = Color(0xFF3498DB);
|
||||
static const exercise = Color(0xFF27AE60);
|
||||
static const sleep = Color(0xFF9B59B6);
|
||||
static const food = Color(0xFFE67E22);
|
||||
|
||||
// Gamification
|
||||
static const gold = Color(0xFFFFD700);
|
||||
static const silver = Color(0xFFC0C0C0);
|
||||
static const bronze = Color(0xFFCD7F32);
|
||||
}
|
||||
```
|
||||
|
||||
### Typography
|
||||
```dart
|
||||
class AppTextStyles {
|
||||
static const heading1 = TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: -0.5,
|
||||
);
|
||||
|
||||
static const heading2 = TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.3,
|
||||
);
|
||||
|
||||
static const body = TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
letterSpacing: 0,
|
||||
);
|
||||
|
||||
static const caption = TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Feature Specifications
|
||||
|
||||
### 1. Dashboard
|
||||
**Layout:**
|
||||
- Greeting with user name
|
||||
- Daily progress ring (overall completion)
|
||||
- Today's routines (grouped by time)
|
||||
- Current streak flame
|
||||
- Points display
|
||||
- Quick add buttons
|
||||
|
||||
**Interactions:**
|
||||
- Swipe routine → Mark done
|
||||
- Tap routine → Details/Edit
|
||||
- Pull down → Refresh
|
||||
- Long press → Quick actions
|
||||
|
||||
### 2. Routine Detail
|
||||
**Layout:**
|
||||
- Icon + Name + Category
|
||||
- Schedule info
|
||||
- Streak counter
|
||||
- History list
|
||||
- Statistics
|
||||
|
||||
**Actions:**
|
||||
- Log completion
|
||||
- Edit routine
|
||||
- Pause/Resume
|
||||
- Delete
|
||||
- Share
|
||||
|
||||
### 3. Add Routine
|
||||
**Steps:**
|
||||
1. Select category
|
||||
2. Enter name/description
|
||||
3. Set schedule (daily/weekly/custom)
|
||||
4. Add reminders
|
||||
5. Assign points
|
||||
6. Choose icon/color
|
||||
|
||||
**Smart Features:**
|
||||
- Template suggestions
|
||||
- Category-based defaults
|
||||
- Icon recommendations
|
||||
|
||||
### 4. Notifications
|
||||
**Types:**
|
||||
- Scheduled reminders
|
||||
- Streak at risk
|
||||
- Achievement unlocked
|
||||
- Refill alerts
|
||||
- Prep reminders
|
||||
|
||||
**Actions:**
|
||||
- Mark done (from notification)
|
||||
- Snooze (5/15/30 min)
|
||||
- Dismiss
|
||||
- View details
|
||||
|
||||
---
|
||||
|
||||
## SMS Parsing Patterns
|
||||
|
||||
### Appointment Pattern
|
||||
```regex
|
||||
(appointment|meeting|consultation).{0,50}(\w+ \d{1,2}).{0,20}(\d{1,2}:\d{2}).{0,50}(at|@)\s+([\w\s]+)
|
||||
```
|
||||
|
||||
### Medication Pattern
|
||||
```regex
|
||||
(prescription|medication|refill).{0,30}(ready|available|pickup).{0,50}(\d+).{0,20}(of|out of).{0,5}(\d+)
|
||||
```
|
||||
|
||||
### Meeting Pattern
|
||||
```regex
|
||||
(meeting|standup|sync).{0,30}(tomorrow|today|(?:\w+day)).{0,20}(\d{1,2}(?::\d{2})?\s*(?:AM|PM)?).{0,50}(in|at)\s+([\w\s]+)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- Data models
|
||||
- Business logic
|
||||
- SMS parsing
|
||||
- Gamification calculations
|
||||
|
||||
### Widget Tests
|
||||
- Screen rendering
|
||||
- User interactions
|
||||
- Form validation
|
||||
- State changes
|
||||
|
||||
### Integration Tests
|
||||
- Database operations
|
||||
- Notification flow
|
||||
- Sync mechanism
|
||||
- SMS parsing
|
||||
|
||||
### E2E Tests
|
||||
- Complete user flows
|
||||
- Critical paths
|
||||
- Edge cases
|
||||
|
||||
---
|
||||
|
||||
## Deployment Plan
|
||||
|
||||
### Android
|
||||
1. Internal Testing (closed)
|
||||
2. Closed Testing (invite-only)
|
||||
3. Open Testing (public beta)
|
||||
4. Production Release
|
||||
|
||||
### PC App
|
||||
1. Windows (installer)
|
||||
2. macOS (DMG)
|
||||
3. Linux (AppImage)
|
||||
|
||||
### Home Assistant
|
||||
1. HACS (Home Assistant Community Store)
|
||||
2. Official integration (if popular)
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
| Metric | Target |
|
||||
|--------|--------|
|
||||
| Daily Active Users | 1000+ (Month 3) |
|
||||
| Routine Completion Rate | 70%+ |
|
||||
| 7-Day Retention | 40%+ |
|
||||
| Average Streak | 5+ days |
|
||||
| App Store Rating | 4.5+ |
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Immediate (Today)
|
||||
1. Initialize Flutter project
|
||||
2. Set up architecture
|
||||
3. Create data models
|
||||
4. Build basic UI
|
||||
|
||||
### This Week
|
||||
1. Complete MVP features
|
||||
2. Add gamification
|
||||
3. Test on device
|
||||
4. Prepare beta
|
||||
|
||||
### Next Week
|
||||
1. Add SMS parsing
|
||||
2. Implement AI suggestions
|
||||
3. Connect backend
|
||||
4. Beta testing
|
||||
|
||||
---
|
||||
|
||||
Ready to start building! 🚀
|
||||
Loading…
Reference in New Issue