Initial commit: Project documentation and git setup

This commit is contained in:
Tomas Dvorak
2026-01-03 18:35:35 +01:00
commit 1639de69d4
51 changed files with 5005 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# Environment variables
.env
.env.local
.env.*.local
# Supabase
supabase/.env
supabase/functions/.env
# Test coverage
coverage/
# Backup files
*.bak
*.backup
# OS generated files
Thumbs.db
Binary file not shown.

After

Width:  |  Height:  |  Size: 605 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

+92
View File
@@ -0,0 +1,92 @@
# LifeTimer  Technical Architecture
This document describes how the Flutter app and Supabase backend are structured.
## 1. Overall approach
- Mobile first, built with Flutter for Android and iOS.
- Supabase used for authentication, Postgres database, storage, and real time features.
- MVVM or Clean Architecture style separation
- Presentation, screens and widgets.
- View models, state management using Provider or Riverpod.
- Data layer, repositories that talk to Supabase clients.
## 2. Flutter project structure
Suggested feature based folder layout
- lib
- core
- theme, routing, localization, error handling, utilities
- data
- supabase client setup, repositories, models, mappers
- features
- auth
- onboarding
- goals
- countdown
- social
- profile
- settings
- analytics
Each feature contains its own presentation widgets, view models, and sub repositories where needed.
## 3. Data flow
- UI widgets send user intents to view models.
- View models call repositories for data access or mutations.
- Repositories call Supabase client methods
- auth api for login, logout, and account management
- Postgres queries or rpc calls for business data
- storage for media uploads
- realtime channels for live updates
- Results flow back up through view models to update state and rebuild widgets.
## 4. Supabase integration
- Single Supabase client instance configured at app start.
- Environment configuration uses separate projects or keys for development, staging, and production.
- Row Level Security used to protect tables so that users only see their own data, unless a profile is explicitly marked public, in which case only a limited subset of non sensitive fields is readable by others.
- Edge functions may be added later for
- computed metrics
- scheduled notifications
- heavy or sensitive operations.
## 5. Offline and caching
- Local persistence of last known state for
- active countdown values
- list of goals and their progress
- Use local database or key value storage package such as Hive or Shared Preferences for small amounts of data.
- Mark pending writes and retry them when connectivity is back.
## 6. Error handling and logging
- Centralized error handler that maps Supabase and network errors to user friendly messages.
- Non fatal errors are logged to an analytics or crash reporting service.
- Critical failures show a fallback screen with safe retry options.
## 7. Testing
- Unit tests for
- countdown calculations
- rules around starting and locking the countdown
- goal progress calculations
- Widget tests for
- goal cards
- countdown screen
- onboarding flow
- Basic integration tests for sign up, creating goals, and starting the countdown.
## 8. Security and privacy architecture
- Use Supabase Auth for all authentication; never store passwords or session tokens outside the auth system.
- Ship only the public anon key in the mobile app; keep the service role key strictly on the server or in Supabase Edge Functions.
- Enforce Row Level Security on all tables, with policies that
- restrict reads and writes to `auth.uid()` for private accounts;
- allow limited read only access to selected fields when `users.is_public_profile = true`.
- Store access tokens only in platform secure storage APIs and never log them.
- Validate all untrusted input on the server side and avoid constructing dynamic SQL from user data.
- Minimize personally identifiable information stored in the database and support account deletion.
- Use structured logging that avoids sensitive fields and route logs to a secure destination.
+271
View File
@@ -0,0 +1,271 @@
# LifeTimer  Database Schema, Supabase
This document expands the schema sketch in project.md into a more detailed proposal for Postgres tables.
Note, naming and exact types can be adjusted when creating migrations.
## 1. users
Core user record.
- id, uuid, primary key
- auth_provider_id, text or json, optional mapping to external providers
- username, text, unique, required
- email, text, unique, required
- avatar_url, text, nullable
- bio, text, nullable
- is_public_profile, boolean, default false
- countdown_start_date, timestamptz, nullable until bucket list is confirmed
- countdown_end_date, timestamptz, nullable until challenge has started
- created_at, timestamptz, default now
- updated_at, timestamptz, default now
Row Level Security
- Users can select and update only their own row.
- Public profile fields such as username and avatar, plus limited stats, can be exposed to other users only when `is_public_profile` is true.
## 2. goals
Represents one goal in the bucket list.
- id, uuid, primary key
- owner_id, uuid, foreign key to users.id
- title, text, required
- description, text, nullable
- progress, integer, 0 to 100, default 0
- location_lat, double precision, nullable
- location_lng, double precision, nullable
- location_name, text, nullable
- image_url, text, nullable
- completed, boolean, default false
- created_at, timestamptz, default now
- updated_at, timestamptz, default now
Constraints
- Each user can have at most 20 goals in total. The countdown can start once there is at least one goal.
## 3. goal_steps
Optional more granular checklist per goal.
- id, uuid, primary key
- goal_id, uuid, foreign key to goals.id
- title, text
- is_done, boolean, default false
- order_index, integer
- created_at, timestamptz, default now
## 4. followers
Social graph between users.
- id, uuid, primary key
- user_id, uuid, the user being followed
- follower_id, uuid, the user who follows
- created_at, timestamptz, default now
Constraint
- unique user_id, follower_id
## 5. activities
Timeline of events used for feeds and analytics.
- id, uuid, primary key
- user_id, uuid, foreign key to users.id
- type, text, for example goal_created, goal_completed, countdown_started
- payload, jsonb, optional extra data
- created_at, timestamptz, default now
## 6. notifications
Optional table for storing notifications when server side delivery is needed.
- id, uuid, primary key
- user_id, uuid, foreign key to users.id
- type, text
- title, text
- body, text
- scheduled_for, timestamptz, nullable
- delivered_at, timestamptz, nullable
## 7. indexes and performance
- Index on goals.owner_id.
- Index on activities.user_id and created_at for feed queries.
- Index on followers.user_id and followers.follower_id.
## 8. Example Row Level Security (RLS) policies
The following snippets show how to enforce the privacy model using PostgreSQL RLS in Supabase.
### 8.1 Enable RLS on tables
```sql
alter table public.users enable row level security;
alter table public.goals enable row level security;
alter table public.goal_steps enable row level security;
alter table public.followers enable row level security;
alter table public.activities enable row level security;
alter table public.notifications enable row level security;
```
### 8.2 users
Users can only see and update their own profile row.
```sql
create policy "Users can select their own profile"
on public.users
for select
using ( auth.uid() = id );
create policy "Users can update their own profile"
on public.users
for update
using ( auth.uid() = id )
with check ( auth.uid() = id );
```
Social and leaderboard queries should **not** select directly from `public.users` with broad access. Instead, implement read-only views or RPC functions that expose only non-sensitive fields for public profiles and are called from secure backend code (for example Supabase Edge Functions using the service role key).
### 8.3 goals
Goals are always private to their owner.
```sql
create policy "Users can read their own goals"
on public.goals
for select
using ( auth.uid() = owner_id );
create policy "Users can insert their own goals"
on public.goals
for insert
with check ( auth.uid() = owner_id );
create policy "Users can update their own goals"
on public.goals
for update
using ( auth.uid() = owner_id )
with check ( auth.uid() = owner_id );
create policy "Users can delete their own goals"
on public.goals
for delete
using ( auth.uid() = owner_id );
```
### 8.4 goal_steps
Access to goal steps is tied to ownership of the parent goal.
```sql
create policy "Users can read steps for their own goals"
on public.goal_steps
for select
using (
exists (
select 1 from public.goals g
where g.id = goal_id and g.owner_id = auth.uid()
)
);
create policy "Users can insert steps for their own goals"
on public.goal_steps
for insert
with check (
exists (
select 1 from public.goals g
where g.id = goal_id and g.owner_id = auth.uid()
)
);
create policy "Users can update steps for their own goals"
on public.goal_steps
for update
using (
exists (
select 1 from public.goals g
where g.id = goal_id and g.owner_id = auth.uid()
)
)
with check (
exists (
select 1 from public.goals g
where g.id = goal_id and g.owner_id = auth.uid()
)
);
create policy "Users can delete steps for their own goals"
on public.goal_steps
for delete
using (
exists (
select 1 from public.goals g
where g.id = goal_id and g.owner_id = auth.uid()
)
);
```
### 8.5 followers
Follow relationships are visible only to the users involved. Either side can remove a relationship.
```sql
create policy "Users can read their follower relationships"
on public.followers
for select
using ( auth.uid() = user_id or auth.uid() = follower_id );
create policy "Users can follow others"
on public.followers
for insert
with check ( auth.uid() = follower_id );
create policy "Users can unfollow or remove followers"
on public.followers
for delete
using ( auth.uid() = user_id or auth.uid() = follower_id );
```
### 8.6 activities
Users see only their own activity timeline. Public feeds and leaderboards should use separate, read-only views or RPC functions that return aggregated activity for public profiles only.
```sql
create policy "Users can read their own activity"
on public.activities
for select
using ( auth.uid() = user_id );
create policy "Users can insert their own activity events"
on public.activities
for insert
with check ( auth.uid() = user_id );
```
### 8.7 notifications
Notifications are private to the recipient user.
```sql
create policy "Users can read their own notifications"
on public.notifications
for select
using ( auth.uid() = user_id );
create policy "Users can receive their own notifications"
on public.notifications
for insert
with check ( auth.uid() = user_id );
create policy "Users can delete their own notifications"
on public.notifications
for delete
using ( auth.uid() = user_id );
```
+61
View File
@@ -0,0 +1,61 @@
# LifeTimer  Development Roadmap
This roadmap breaks down the phases from project.md into concrete milestones.
## Phase 0  Planning and foundations
Status, in progress
- [x] Clarify product vision and constraints.
- [x] Create high level project plan and documentation.
- [ ] Set up Flutter project with base folder structure.
- [ ] Configure Supabase project, environment variables, and basic tables.
- [ ] Set up version control and basic continuous integration.
## Phase 1  MVP core experience
Goal, a private personal countdown with a bucket list of up to 20 goals.
- [ ] Implement authentication and profile editing.
- [ ] Implement bucket list creation and editing, limited to 20 goals.
- [ ] Implement confirmation flow that locks the bucket list and starts the countdown.
- [ ] Implement home countdown screen with large timer and percentage of time elapsed.
- [ ] Implement basic goals list and goal detail with progress and completion.
- [ ] Implement local notifications for daily or weekly reminders.
- [ ] Add simple analytics events for key actions.
Exit criteria
- A user can sign up, create and confirm a bucket list, see the countdown, and track progress on their goals on at least one platform.
## Phase 2  Social and motivation
Goal, optional social layer that motivates users without forcing them to share.
- [ ] Implement following system and basic public profiles.
- [ ] Implement activity feed of public milestones.
- [ ] Implement leaderboards for goals completed and active streaks.
- [ ] Add achievements and badges.
- [ ] Extend notification logic for social events.
Exit criteria
- Users can opt in to sharing and see others public progress in a simple, motivating feed.
## Phase 3  Advanced experience
Goal, make the app richer and more insightful.
- [ ] Add charts and insights for progress versus time.
- [ ] Integrate image APIs for automatic goal cover images.
- [ ] Add map integration for location based goals.
- [ ] Improve offline support and caching.
- [ ] Extend settings, themes, and personalization.
## Phase 4  Polish and release
- [ ] Accessibility review and improvements.
- [ ] Performance tuning on low end devices.
- [ ] App Store and Play Store preparation, listings, screenshots, and policies.
- [ ] Beta testing and bug fixing.
- [ ] Public release and monitoring.
+215
View File
@@ -0,0 +1,215 @@
# LifeTimer — Flutter Project Structure
This document describes the recommended folder and file structure for the LifeTimer Flutter app, aligned with the architecture and flows.
Assumption: you will start from a standard `flutter create lifetimer` project, then adapt the `lib/` directory as follows.
---
## 1. High level layout
- `lib/`
- `main.dart` — app entrypoint, initializes Supabase and runs `LifeTimerApp`.
- `bootstrap/` — initialization helpers (Supabase client, dependency injection, env config).
- `core/` — cross cutting concerns (theme, routing, localization, widgets, utils, error handling).
- `data/` — shared data layer code (models, repositories, Supabase clients).
- `features/` — feature modules (auth, onboarding, goals, countdown, social, profile, settings, analytics).
---
## 2. lib/main.dart
Responsibilities:
- Ensure Flutter bindings are initialized.
- Initialize Supabase client (using config from `bootstrap/`).
- Run the `LifeTimerApp` widget.
File:
- `lib/main.dart`
---
## 3. Bootstrap and configuration
Folder:
- `lib/bootstrap/`
- `bootstrap.dart` — high level `bootstrap()` function called from `main.dart` (sets up Supabase, error handlers, env).
- `supabase_client.dart` — creates and exposes a configured Supabase client instance.
- `env.dart` — holds environment configuration (Supabase URL, anon key, build flavor hooks).
---
## 4. Core module
Folder:
- `lib/core/`
- `theme/`
- `app_theme.dart` — light/dark theme definitions, colors, typography.
- `routing/`
- `app_router.dart` — central route definitions and navigation helpers.
- `localization/`
- `l10n.dart` — localization setup (generated or custom wrapper).
- `widgets/`
- `primary_button.dart` — reusable CTA button.
- `app_scaffold.dart` — base scaffold with consistent background and app bar style.
- `bottom_nav_scaffold.dart` — scaffold wiring bottom navigation.
- `loading_indicator.dart` — standard loading widget.
- `empty_state.dart` — reusable empty state widget.
- `errors/`
- `failure.dart` — domain error types.
- `error_mapper.dart` — maps Supabase / network errors to user friendly messages.
- `utils/`
- `date_time_utils.dart` — countdown calculations, formatting.
- `validators.dart` — input validation helpers.
- `state/` (if using Riverpod/Provider wrappers)
- `providers.dart` — top level providers (Supabase client, theme mode, auth state).
---
## 5. Data layer
Folder:
- `lib/data/`
- `supabase/`
- `supabase_client_provider.dart` — exposes Supabase client to the app (e.g., via Riverpod/Provider).
- `models/`
- `user_model.dart` — app level User model.
- `goal_model.dart` — Goal model.
- `goal_step_model.dart` — GoalStep model.
- `activity_model.dart` — Activity model.
- `repositories/`
- `auth_repository.dart` — login, logout, token refresh.
- `user_repository.dart` — profile read/update, visibility toggle.
- `goals_repository.dart` — CRUD for goals and steps, enforce 120 limit.
- `countdown_repository.dart` — start countdown, compute remaining time.
- `social_repository.dart` — followers, feeds, leaderboards (Phase 2+).
- `notifications_repository.dart` — notification preferences and tokens.
Repositories hide Supabase queries behind a clean API, so features do not talk to Supabase directly.
---
## 6. Feature modules
Each feature lives under `lib/features/<feature_name>/` with a simple structure:
- `presentation/` — screens and widgets.
- `application/` — state management (view models, controllers, providers).
- `domain/` (optional) — feature specific entities and logic.
### 6.1 Auth feature
- `lib/features/auth/presentation/`
- `auth_gate.dart` — decides whether to show auth screens or main app based on session.
- `sign_in_screen.dart`
- `sign_up_screen.dart`
- `auth_loading_screen.dart` — transient during OAuth redirects.
- `lib/features/auth/application/`
- `auth_controller.dart` — wraps `auth_repository`, exposes current user/session state.
### 6.2 Onboarding feature
- `lib/features/onboarding/presentation/`
- `onboarding_intro_screen.dart`
- `onboarding_how_it_works_screen.dart`
- `onboarding_motivation_screen.dart`
- `lib/features/onboarding/application/`
- `onboarding_controller.dart` — tracks whether onboarding is completed.
### 6.3 Goals feature
- `lib/features/goals/presentation/`
- `goals_list_screen.dart` — list of goals, empty state, add/edit buttons.
- `goal_edit_screen.dart` — create/edit goal (title, description, location, image, milestones).
- `goal_detail_screen.dart` — view and update progress, mark as completed.
- `lib/features/goals/application/`
- `goals_controller.dart` — loads goals, enforces max 20, exposes list/state.
- `goal_detail_controller.dart` — state for a single goal.
### 6.4 Countdown feature
- `lib/features/countdown/presentation/`
- `home_countdown_screen.dart` — main world-time inspired countdown UI.
- `countdown_summary_screen.dart` — shown after countdown ends, summary stats.
- `lib/features/countdown/application/`
- `countdown_controller.dart` — calculates remaining time, exposes streams/timers.
### 6.5 Social feature (Phase 2+)
- `lib/features/social/presentation/`
- `social_feed_screen.dart`
- `leaderboards_screen.dart`
- `public_profile_screen.dart`
- `lib/features/social/application/`
- `social_feed_controller.dart`
- `leaderboards_controller.dart`
### 6.6 Profile feature
- `lib/features/profile/presentation/`
- `profile_screen.dart` — shows own profile, countdown info, stats.
- `lib/features/profile/application/`
- `profile_controller.dart` — loads and updates profile, visibility, avatar.
### 6.7 Settings feature
- `lib/features/settings/presentation/`
- `settings_home_screen.dart`
- `account_settings_screen.dart`
- `appearance_settings_screen.dart`
- `notification_settings_screen.dart`
- `privacy_settings_screen.dart` — global Public/Private toggle, blocking.
- `about_challenge_screen.dart`
- `lib/features/settings/application/`
- `settings_controller.dart` — wraps repositories for user preferences.
### 6.8 Analytics / insights (Phase 3)
- `lib/features/analytics/presentation/`
- `insights_screen.dart` — charts and stats.
- `lib/features/analytics/application/`
- `insights_controller.dart`
---
## 7. Navigation and route names
- Central router file: `lib/core/routing/app_router.dart`.
- Define named routes for key screens, grouped by feature, e.g.:
- `/``AuthGate` (decides between auth vs main app).
- `/onboarding/*` for onboarding screens.
- `/home``HomeCountdownScreen`.
- `/goals`, `/goals/:id`, `/goals/:id/edit`.
- `/social`, `/social/leaderboards`, `/u/:username`.
- `/settings/*` for settings sections.
The router uses the navigation library you choose (Navigator 2.0, go_router, etc.); this file is the single source of truth for destinations.
---
## 8. State management
If you use Riverpod or Provider:
- Global providers in `lib/core/state/providers.dart`:
- `supabaseClientProvider`
- `authControllerProvider`
- `themeModeProvider`
- Feature specific providers next to controllers in their `application/` folders.
---
## 9. Testing structure
Mirror the feature structure under `test/`:
- `test/core/` — utilities, date/time tests, error mapping tests.
- `test/data/` — repository tests with mocked Supabase client.
- `test/features/<feature_name>/` — unit and widget tests for each feature.
This structure keeps the app modular and makes it easy to grow features (e.g., adding more social or analytics functionality) without losing clarity.
+45
View File
@@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
+30
View File
@@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "f6ff1529fd6d8af5f706051d9251ac9231c83407"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
- platform: linux
create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
+133
View File
@@ -0,0 +1,133 @@
# LifeTimer Flutter Project
## Overview
LifeTimer is a gamified life countdown app where users create a bucket list (up to 20 entries) and start a 1356-day countdown once they finalize their goals. The countdown cannot be stopped, paused, or extended.
## Project Structure
This Flutter project follows a clean architecture with feature-based organization:
```
lib/
├── main.dart # App entry point
├── bootstrap/ # Initialization
│ ├── bootstrap.dart
│ ├── env.dart
│ └── supabase_client.dart
├── core/ # Cross-cutting concerns
│ ├── theme/
│ │ └── app_theme.dart
│ ├── routing/
│ │ └── app_router.dart
│ ├── widgets/
│ │ └── primary_button.dart
│ └── state/
│ └── providers.dart
├── data/ # Data layer
│ ├── models/
│ │ ├── user_model.dart
│ │ └── goal_model.dart
│ └── repositories/
│ └── auth_repository.dart
└── features/ # Feature modules
├── auth/
├── onboarding/
├── goals/
├── countdown/
├── social/
├── profile/
└── settings/
```
## Tech Stack
- **Framework**: Flutter
- **State Management**: Riverpod
- **Backend**: Supabase (Auth, Database, Storage, Realtime)
- **Navigation**: Go Router
- **Local Storage**: Hive
- **Maps**: Google Maps Flutter
- **Notifications**: Flutter Local Notifications
## Getting Started
### Prerequisites
1. Flutter SDK (>=3.10.0)
2. Dart SDK (>=3.0.0)
3. Supabase project
### Setup
1. Clone this repository
2. Install dependencies:
```bash
flutter pub get
```
3. Set up environment variables:
Create a `.env` file or use build arguments:
```bash
flutter run --dart-define=SUPABASE_URL=your_url --dart-define=SUPABASE_ANON_KEY=your_key
```
4. Run the app:
```bash
flutter run
```
## Key Features
### Phase 1 (MVP)
- [x] User authentication (email, Google, Apple)
- [x] Bucket list creation (up to 20 goals)
- [x] 1356-day countdown timer
- [x] Goal progress tracking
- [x] Basic profile management
### Phase 2 (Social)
- [ ] Public/private profiles
- [ ] Social feed
- [ ] Leaderboards
- [ ] Following system
### Phase 3 (Advanced)
- [ ] Charts and analytics
- [ ] Image API integration
- [ ] Map integration for location-based goals
- [ ] Offline support
## Database Schema
The app uses Supabase PostgreSQL with the following main tables:
- `users` - User profiles and countdown data
- `goals` - Bucket list items
- `goal_steps` - Granular goal milestones
- `followers` - Social relationships
- `activities` - Timeline events
## Architecture Patterns
- **MVVM/Clean Architecture** with clear separation of concerns
- **Repository Pattern** for data access
- **Provider/StateNotifier** for state management
- **Feature-based organization** for scalability
## Development Notes
- All screens are currently placeholder implementations
- Core structure and dependencies are set up
- Authentication flow is partially implemented
- Database models and repositories are defined
- Navigation structure is in place
## Next Steps
1. Complete authentication implementation
2. Implement bucket list creation and management
3. Build countdown timer functionality
4. Add goal progress tracking
5. Implement social features (Phase 2)
6. Add advanced analytics (Phase 3)
+28
View File
@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
+15
View File
@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'env.dart';
import 'supabase_client.dart';
Future<void> bootstrap() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: Env.supabaseUrl,
anonKey: Env.supabaseAnonKey,
);
initializeSupabaseClient();
}
+11
View File
@@ -0,0 +1,11 @@
class Env {
static const String supabaseUrl = String.fromEnvironment(
'SUPABASE_URL',
defaultValue: 'https://your-project.supabase.co',
);
static const String supabaseAnonKey = String.fromEnvironment(
'SUPABASE_ANON_KEY',
defaultValue: 'your-anon-key',
);
}
@@ -0,0 +1,8 @@
import 'package:supabase_flutter/supabase_flutter.dart';
void initializeSupabaseClient() {
// Additional client setup if needed
// For now, we use the default Supabase.instance.client
}
SupabaseClient get supabaseClient => Supabase.instance.client;
@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../features/auth/presentation/auth_gate.dart';
import '../../features/onboarding/presentation/onboarding_intro_screen.dart';
import '../../features/countdown/presentation/home_countdown_screen.dart';
import '../../features/goals/presentation/goals_list_screen.dart';
import '../../features/social/presentation/social_feed_screen.dart';
import '../../features/profile/presentation/profile_screen.dart';
import '../../features/settings/presentation/settings_home_screen.dart';
final appRouterProvider = Provider<GoRouter>((ref) {
return GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const AuthGate(),
),
GoRoute(
path: '/onboarding',
builder: (context, state) => const OnboardingIntroScreen(),
),
GoRoute(
path: '/home',
builder: (context, state) => const HomeCountdownScreen(),
),
GoRoute(
path: '/goals',
builder: (context, state) => const GoalsListScreen(),
),
GoRoute(
path: '/social',
builder: (context, state) => const SocialFeedScreen(),
),
GoRoute(
path: '/profile',
builder: (context, state) => const ProfileScreen(),
),
GoRoute(
path: '/settings',
builder: (context, state) => const SettingsHomeScreen(),
),
],
errorBuilder: (context, state) => Scaffold(
body: Center(
child: Text('Error: ${state.error}'),
),
),
);
});
+4
View File
@@ -0,0 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final themeModeProvider = StateProvider<ThemeMode>((ref) => ThemeMode.system);
+161
View File
@@ -0,0 +1,161 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class AppTheme {
static const Color primaryColor = Color(0xFF6366F1);
static const Color secondaryColor = Color(0xFF8B5CF6);
static const Color accentColor = Color(0xFFEC4899);
static const Color backgroundColor = Color(0xFFFAFAFA);
static const Color surfaceColor = Color(0xFFFFFFFF);
static const Color errorColor = Color(0xFFEF4444);
static const Color warningColor = Color(0xFFF59E0B);
static const Color successColor = Color(0xFF10B981);
static const ColorScheme lightColorScheme = ColorScheme(
brightness: Brightness.light,
primary: primaryColor,
onPrimary: Color(0xFFFFFFFF),
secondary: secondaryColor,
onSecondary: Color(0xFFFFFFFF),
error: errorColor,
onError: Color(0xFFFFFFFF),
surface: surfaceColor,
onSurface: Color(0xFF1F2937),
background: backgroundColor,
onBackground: Color(0xFF1F2937),
);
static const ColorScheme darkColorScheme = ColorScheme(
brightness: Brightness.dark,
primary: primaryColor,
onPrimary: Color(0xFFFFFFFF),
secondary: secondaryColor,
onSecondary: Color(0xFFFFFFFF),
error: errorColor,
onError: Color(0xFFFFFFFF),
surface: Color(0xFF1F2937),
onSurface: Color(0xFFF9FAFB),
background: Color(0xFF111827),
onBackground: Color(0xFFF9FAFB),
);
static ThemeData get light {
return ThemeData(
useMaterial3: true,
colorScheme: lightColorScheme,
appBarTheme: const AppBarTheme(
backgroundColor: surfaceColor,
foregroundColor: Color(0xFF1F2937),
elevation: 0,
systemOverlayStyle: SystemUiOverlayStyle.dark,
),
cardTheme: CardThemeData(
color: surfaceColor,
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor,
foregroundColor: Color(0xFFFFFFFF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
),
textTheme: const TextTheme(
displayLarge: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Color(0xFF1F2937),
),
displayMedium: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Color(0xFF1F2937),
),
headlineLarge: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2937),
),
headlineMedium: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2937),
),
bodyLarge: TextStyle(
fontSize: 16,
color: Color(0xFF4B5563),
),
bodyMedium: TextStyle(
fontSize: 14,
color: Color(0xFF6B7280),
),
),
);
}
static ThemeData get dark {
return ThemeData(
useMaterial3: true,
colorScheme: darkColorScheme,
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xFF1F2937),
foregroundColor: Color(0xFFF9FAFB),
elevation: 0,
systemOverlayStyle: SystemUiOverlayStyle.light,
),
cardTheme: CardThemeData(
color: const Color(0xFF374151),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor,
foregroundColor: Color(0xFFFFFFFF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
),
textTheme: const TextTheme(
displayLarge: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Color(0xFFF9FAFB),
),
displayMedium: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Color(0xFFF9FAFB),
),
headlineLarge: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color: Color(0xFFF9FAFB),
),
headlineMedium: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Color(0xFFF9FAFB),
),
bodyLarge: TextStyle(
fontSize: 16,
color: Color(0xFFD1D5DB),
),
bodyMedium: TextStyle(
fontSize: 14,
color: Color(0xFF9CA3AF),
),
),
);
}
}
@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
class PrimaryButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
final bool isLoading;
final bool isDisabled;
final Color? backgroundColor;
final Color? foregroundColor;
final double? width;
final double? height;
const PrimaryButton({
super.key,
required this.text,
required this.onPressed,
this.isLoading = false,
this.isDisabled = false,
this.backgroundColor,
this.foregroundColor,
this.width,
this.height,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
height: height ?? 48,
child: ElevatedButton(
onPressed: (isLoading || isDisabled) ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: isLoading
? SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
foregroundColor ?? Theme.of(context).colorScheme.onPrimary,
),
),
)
: Text(text),
),
);
}
}
+81
View File
@@ -0,0 +1,81 @@
import 'package:equatable/equatable.dart';
class Goal extends Equatable {
final String id;
final String ownerId;
final String title;
final String? description;
final int progress;
final double? locationLat;
final double? locationLng;
final String? locationName;
final String? imageUrl;
final bool completed;
final DateTime createdAt;
final DateTime updatedAt;
const Goal({
required this.id,
required this.ownerId,
required this.title,
this.description,
this.progress = 0,
this.locationLat,
this.locationLng,
this.locationName,
this.imageUrl,
this.completed = false,
required this.createdAt,
required this.updatedAt,
});
bool get hasLocation => locationLat != null && locationLng != null;
bool get hasImage => imageUrl != null && imageUrl!.isNotEmpty;
Goal copyWith({
String? id,
String? ownerId,
String? title,
String? description,
int? progress,
double? locationLat,
double? locationLng,
String? locationName,
String? imageUrl,
bool? completed,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return Goal(
id: id ?? this.id,
ownerId: ownerId ?? this.ownerId,
title: title ?? this.title,
description: description ?? this.description,
progress: progress ?? this.progress,
locationLat: locationLat ?? this.locationLat,
locationLng: locationLng ?? this.locationLng,
locationName: locationName ?? this.locationName,
imageUrl: imageUrl ?? this.imageUrl,
completed: completed ?? this.completed,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
@override
List<Object?> get props => [
id,
ownerId,
title,
description,
progress,
locationLat,
locationLng,
locationName,
imageUrl,
completed,
createdAt,
updatedAt,
];
}
+79
View File
@@ -0,0 +1,79 @@
import 'package:equatable/equatable.dart';
class User extends Equatable {
final String id;
final String username;
final String email;
final String? avatarUrl;
final String? bio;
final bool isPublicProfile;
final DateTime? countdownStartDate;
final DateTime? countdownEndDate;
final DateTime createdAt;
final DateTime updatedAt;
const User({
required this.id,
required this.username,
required this.email,
this.avatarUrl,
this.bio,
this.isPublicProfile = false,
this.countdownStartDate,
this.countdownEndDate,
required this.createdAt,
required this.updatedAt,
});
bool get hasCountdownStarted => countdownStartDate != null;
bool get isCountdownActive {
if (!hasCountdownStarted || countdownEndDate == null) return false;
return DateTime.now().isBefore(countdownEndDate!);
}
int? get daysRemaining {
if (!isCountdownActive) return null;
return countdownEndDate!.difference(DateTime.now()).inDays;
}
User copyWith({
String? id,
String? username,
String? email,
String? avatarUrl,
String? bio,
bool? isPublicProfile,
DateTime? countdownStartDate,
DateTime? countdownEndDate,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return User(
id: id ?? this.id,
username: username ?? this.username,
email: email ?? this.email,
avatarUrl: avatarUrl ?? this.avatarUrl,
bio: bio ?? this.bio,
isPublicProfile: isPublicProfile ?? this.isPublicProfile,
countdownStartDate: countdownStartDate ?? this.countdownStartDate,
countdownEndDate: countdownEndDate ?? this.countdownEndDate,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
@override
List<Object?> get props => [
id,
username,
email,
avatarUrl,
bio,
isPublicProfile,
countdownStartDate,
countdownEndDate,
createdAt,
updatedAt,
];
}
@@ -0,0 +1,125 @@
import '../models/user_model.dart';
import '../../bootstrap/supabase_client.dart';
import 'package:supabase_flutter/supabase_flutter.dart' hide User;
class AuthRepository {
final SupabaseClient _client;
AuthRepository([SupabaseClient? client]) : _client = client ?? supabaseClient;
Stream<User?> get authStateChanges {
return _client.auth.onAuthStateChange.map((data) {
final session = data.session;
if (session?.user != null) {
return _mapSupabaseUserToAppUser(session!.user);
}
return null;
});
}
User? get currentUser {
final user = _client.auth.currentUser;
return user != null ? _mapSupabaseUserToAppUser(user) : null;
}
Future<void> signInWithEmail(String email, String password) async {
await _client.auth.signInWithPassword(email: email, password: password);
}
Future<void> signUpWithEmail(String email, String password, String username) async {
final response = await _client.auth.signUp(
email: email,
password: password,
data: {'username': username},
);
if (response.user != null) {
await _createUserProfile(response.user!.id, username, email);
}
}
Future<void> signInWithGoogle() async {
// TODO: Implement Google OAuth
// await _client.auth.signInWithOAuth(OAuthProvider.google);
throw UnimplementedError('Google OAuth not implemented yet');
}
Future<void> signInWithApple() async {
// TODO: Implement Apple OAuth
// await _client.auth.signInWithOAuth(OAuthProvider.apple);
throw UnimplementedError('Apple OAuth not implemented yet');
}
Future<void> signOut() async {
await _client.auth.signOut();
}
Future<void> resetPassword(String email) async {
await _client.auth.resetPasswordForEmail(email);
}
Future<void> updateProfile({
String? username,
String? bio,
String? avatarUrl,
bool? isPublicProfile,
}) async {
final userId = _client.auth.currentUser?.id;
if (userId == null) throw Exception('User not authenticated');
final updates = <String, dynamic>{};
if (username != null) updates['username'] = username;
if (bio != null) updates['bio'] = bio;
if (avatarUrl != null) updates['avatar_url'] = avatarUrl;
if (isPublicProfile != null) updates['is_public_profile'] = isPublicProfile;
updates['updated_at'] = DateTime.now().toIso8601String();
await _client
.from('users')
.update(updates)
.eq('id', userId);
}
Future<User> _createUserProfile(String userId, String username, String email) async {
final now = DateTime.now().toIso8601String();
final response = await _client.from('users').insert({
'id': userId,
'username': username,
'email': email,
'created_at': now,
'updated_at': now,
}).select().single();
return _mapSupabaseDataToUser(response);
}
User _mapSupabaseUserToAppUser(dynamic supabaseUser) {
return User(
id: supabaseUser.id,
username: supabaseUser.userMetadata?['username'] ?? '',
email: supabaseUser.email ?? '',
createdAt: DateTime.tryParse(supabaseUser.createdAt ?? '') ?? DateTime.now(),
updatedAt: DateTime.tryParse(supabaseUser.updatedAt ?? '') ?? DateTime.now(),
);
}
User _mapSupabaseDataToUser(Map<String, dynamic> data) {
return User(
id: data['id'],
username: data['username'],
email: data['email'],
avatarUrl: data['avatar_url'],
bio: data['bio'],
isPublicProfile: data['is_public_profile'] ?? false,
countdownStartDate: data['countdown_start_date'] != null
? DateTime.parse(data['countdown_start_date'])
: null,
countdownEndDate: data['countdown_end_date'] != null
? DateTime.parse(data['countdown_end_date'])
: null,
createdAt: DateTime.parse(data['created_at']),
updatedAt: DateTime.parse(data['updated_at']),
);
}
}
@@ -0,0 +1,64 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../data/repositories/auth_repository.dart';
import '../../../data/models/user_model.dart';
final authControllerProvider = StateNotifierProvider<AuthController, User?>((ref) {
return AuthController(ref.read(authRepositoryProvider));
});
final authRepositoryProvider = Provider<AuthRepository>((ref) {
return AuthRepository(/* SupabaseClient instance will be injected */);
});
class AuthController extends StateNotifier<User?> {
final AuthRepository _authRepository;
AuthController(this._authRepository) : super(null) {
_init();
}
void _init() {
state = _authRepository.currentUser;
_authRepository.authStateChanges.listen((user) {
state = user;
});
}
Future<void> signInWithEmail(String email, String password) async {
await _authRepository.signInWithEmail(email, password);
}
Future<void> signUpWithEmail(String email, String password, String username) async {
await _authRepository.signUpWithEmail(email, password, username);
}
Future<void> signInWithGoogle() async {
await _authRepository.signInWithGoogle();
}
Future<void> signInWithApple() async {
await _authRepository.signInWithApple();
}
Future<void> signOut() async {
await _authRepository.signOut();
}
Future<void> resetPassword(String email) async {
await _authRepository.resetPassword(email);
}
Future<void> updateProfile({
String? username,
String? bio,
String? avatarUrl,
bool? isPublicProfile,
}) async {
await _authRepository.updateProfile(
username: username,
bio: bio,
avatarUrl: avatarUrl,
isPublicProfile: isPublicProfile,
);
}
}
@@ -0,0 +1,134 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../application/auth_controller.dart';
import '../../onboarding/presentation/onboarding_intro_screen.dart';
class AuthGate extends ConsumerWidget {
const AuthGate({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authControllerProvider);
if (authState == null) {
return const SignInScreen();
}
return const OnboardingIntroScreen();
}
}
class SignInScreen extends ConsumerStatefulWidget {
const SignInScreen({super.key});
@override
ConsumerState<SignInScreen> createState() => _SignInScreenState();
}
class _SignInScreenState extends ConsumerState<SignInScreen> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> _signIn() async {
if (!_formKey.currentState!.validate()) return;
setState(() => _isLoading = true);
try {
await ref.read(authControllerProvider.notifier).signInWithEmail(
_emailController.text.trim(),
_passwordController.text,
);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
} finally {
if (mounted) setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('LifeTimer'),
),
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'Welcome Back',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(),
),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
return null;
},
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _isLoading ? null : _signIn,
child: _isLoading
? const CircularProgressIndicator()
: const Text('Sign In'),
),
const SizedBox(height: 16),
TextButton(
onPressed: () {
// Navigate to sign up
},
child: const Text('Don\'t have an account? Sign Up'),
),
],
),
),
),
);
}
}
@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
class HomeCountdownScreen extends StatelessWidget {
const HomeCountdownScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('LifeTimer'),
),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'1356',
style: TextStyle(
fontSize: 72,
fontWeight: FontWeight.bold,
),
),
Text(
'days remaining',
style: TextStyle(fontSize: 24),
),
SizedBox(height: 32),
Text(
'Your countdown starts here',
style: TextStyle(fontSize: 18),
),
],
),
),
);
}
}
@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
class GoalsListScreen extends StatelessWidget {
const GoalsListScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Goals'),
),
body: const Center(
child: Text('Goals List - Coming Soon'),
),
);
}
}
@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
class OnboardingIntroScreen extends StatelessWidget {
const OnboardingIntroScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('LifeTimer'),
),
body: const Padding(
padding: EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Welcome to LifeTimer',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
SizedBox(height: 16),
Text(
'Your 1356-day journey starts here.\nCreate your bucket list and begin your countdown.',
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
],
),
),
);
}
}
@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Profile'),
),
body: const Center(
child: Text('Profile - Coming Soon'),
),
);
}
}
@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
class SettingsHomeScreen extends StatelessWidget {
const SettingsHomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Settings'),
),
body: const Center(
child: Text('Settings - Coming Soon'),
),
);
}
}
@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
class SocialFeedScreen extends StatelessWidget {
const SocialFeedScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Social'),
),
body: const Center(
child: Text('Social Feed - Coming Soon'),
),
);
}
}
+37
View File
@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'bootstrap/bootstrap.dart';
import 'core/theme/app_theme.dart';
import 'core/routing/app_router.dart';
import 'core/state/providers.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await bootstrap();
runApp(
const ProviderScope(
child: LifeTimerApp(),
),
);
}
class LifeTimerApp extends ConsumerWidget {
const LifeTimerApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.watch(appRouterProvider);
final themeMode = ref.watch(themeModeProvider);
return MaterialApp.router(
title: 'LifeTimer',
debugShowCheckedModeBanner: false,
theme: AppTheme.light,
darkTheme: AppTheme.dark,
themeMode: themeMode,
routerConfig: router,
);
}
}
+1
View File
@@ -0,0 +1 @@
flutter/ephemeral
+128
View File
@@ -0,0 +1,128 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "lifetimer")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.lifetimer")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
+88
View File
@@ -0,0 +1,88 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)
@@ -0,0 +1,23 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <file_selector_linux/file_selector_plugin.h>
#include <gtk/gtk_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) gtk_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
gtk_plugin_register_with_registrar(gtk_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}
@@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_
@@ -0,0 +1,26 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux
gtk
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)
+26
View File
@@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX)
# Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add preprocessor definitions for the application ID.
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+6
View File
@@ -0,0 +1,6 @@
#include "my_application.h"
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}
+148
View File
@@ -0,0 +1,148 @@
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Called when first Flutter frame received.
static void first_frame_cb(MyApplication* self, FlView* view) {
gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
}
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
// desktop).
// If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "lifetimer");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "lifetimer");
}
gtk_window_set_default_size(window, 1280, 720);
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(
project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
GdkRGBA background_color;
// Background defaults to black, override it here if necessary, e.g. #00000000
// for transparent.
gdk_rgba_parse(&background_color, "#000000");
fl_view_set_background_color(view, &background_color);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
// Show the window when Flutter renders.
// Requires the view to be realized so we can start rendering.
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb),
self);
gtk_widget_realize(GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application,
gchar*** arguments,
int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
// Implements GApplication::startup.
static void my_application_startup(GApplication* application) {
// MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application startup.
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
}
// Implements GApplication::shutdown.
static void my_application_shutdown(GApplication* application) {
// MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application shutdown.
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line =
my_application_local_command_line;
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
// Set the program name to the application ID, which helps various systems
// like GTK and desktop environments map this running application to its
// corresponding .desktop file. This ensures better integration by allowing
// the application to be recognized beyond its binary name.
g_set_prgname(APPLICATION_ID);
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID, "flags",
G_APPLICATION_NON_UNIQUE, nullptr));
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication,
my_application,
MY,
APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_
File diff suppressed because it is too large Load Diff
+72
View File
@@ -0,0 +1,72 @@
name: lifetimer
description: A gamified life countdown app with 1356-day challenge and bucket list tracking.
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
flutter: ">=3.10.0"
dependencies:
flutter:
sdk: flutter
# State Management
flutter_riverpod: ^2.4.9
riverpod_annotation: ^2.3.3
# Supabase Backend
supabase_flutter: ^1.10.24
# Navigation
go_router: ^12.1.3
# UI Components
cupertino_icons: ^1.0.2
material_color_utilities: ^0.11.1
# Local Storage & Caching
hive: ^2.2.3
hive_flutter: ^1.1.0
path_provider: ^2.1.1
# Utilities
intl: ^0.18.1
uuid: ^4.2.1
equatable: ^2.0.5
# Image Handling
cached_network_image: ^3.3.0
image_picker: ^1.0.4
# Maps & Location
geolocator: ^10.1.0
google_maps_flutter: ^2.5.0
# Notifications
flutter_local_notifications: ^16.3.0
# Charts & Analytics
fl_chart: ^0.65.0
# Testing
mockito: ^5.4.4
integration_test:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
# Code Generation
riverpod_generator: ^2.3.9
build_runner: ^2.4.7
hive_generator: ^2.0.1
flutter:
uses-material-design: true
assets:
- assets/images/
- assets/icons/
+30
View File
@@ -0,0 +1,30 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:lifetimer/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
+190
View File
@@ -0,0 +1,190 @@
# LifeTimer — Navigation and User Flows
This document describes the main screens, navigation, and user journeys for the LifeTimer app.
---
## 1. Screen map and navigation
### 1.1 Global structure
- **Splash / Launch**
- Lightweight loading and environment check.
- Determines whether to show onboarding or go directly to the app.
- **Onboarding flow**
- `OnboardingIntro` — explain the 1356day challenge concept.
- `OnboardingHowItWorks` — steps: set 120 goals → lock list → 1356day countdown.
- `OnboardingMotivation` — motivational examples, visuals.
- Final screen leads to authentication.
- **Authentication**
- `AuthChoice` — sign up / log in buttons, Google and Apple options.
- `SignUpEmail` — email + password registration.
- `SignInEmail` — email + password login.
- `OAuthRedirect` — transient screen while completing Google / Apple signin.
- After auth, go to profile or home depending on completion state.
- **Profile setup (first time)**
- `CreateProfile` — avatar, username (unique), short bio.
- Once done, proceed to bucket list intro.
- **Main app shell**
- Bottom navigation with 45 tabs:
- **Home** — countdown and highlevel summary.
- **Goals** — bucket list and goal management.
- **Social** — feed, leaderboards (Phase 2+).
- **Profile** — own profile, stats, settings entry.
- **Insights (optional)** — charts and analytics (Phase 3).
The bottom nav is persistent after login; individual flows push screens on top of the tab stacks.
---
## 2. Core journey: new user to active countdown
### 2.1 First session flow
1. **Launch → Onboarding**
- `Splash``OnboardingIntro`.
- User swipes or taps Next through 23 explanatory screens.
- Final onboarding screen CTA: **Get started**`AuthChoice`.
2. **Authentication**
- Choose **Sign up with email**, **Google**, or **Apple**.
- On success, navigate to `CreateProfile`.
3. **Profile setup**
- Enter avatar (optional), username (required, uniqueness validated), and short bio.
- CTA **Continue**`BucketListIntro`.
4. **Bucket list introduction**
- `BucketListIntro` explains rules:
- You can create between 1 and 20 goals.
- Countdown only starts when your list is finalized and you have at least one goal.
- Once started, cannot be paused or reset.
- CTA **Start creating my list**`GoalsListEdit` (empty state).
5. **Creating goals**
- `GoalsListEdit` (tab: Goals) shows empty state card: “No goals yet. Add your first goal”.
- Tap **Add goal**`GoalEdit` screen.
6. **Goal edit flow**
- Fields: Title (required), Description, Location, Image, Milestones.
- User can add milestones / steps.
- CTA **Save goal** returns to `GoalsListEdit`.
- User repeats Add goal until satisfied (up to 20; UI shows **x / 20 goals created**; the countdown start action becomes available once x ≥ 1).
7. **Confirming the bucket list**
- When user has created at least one goal, a prominent CTA appears: **Finalize list and start 1356day challenge**.
- Tapping CTA opens `ConfirmBucketListDialog`:
- Summary: number of goals, explanation of irreversible start.
- Options:
- **Start countdown** — sets `countdown_start_date` and `countdown_end_date`, navigates to `HomeCountdown`.
- **Review goals** — returns to `GoalsListEdit`.
8. **First countdown view**
- `HomeCountdown` shows:
- Very large remaining days (worldclock inspired layout).
- Smaller breakdown: days, hours, minutes, seconds.
- Progress ring or bar showing percentage of time elapsed.
- Short motivational message.
- Primary CTA: **View my goals** (to Goals tab).
### 2.2 If bucket list is not finalized
- If a user leaves the app before finalizing:
- On next login, `Home` shows “Your challenge hasnt started yet” with progress in creating goals (e.g., 5 / 20 goals completed) and a CTA to **Finish my list**`GoalsListEdit`.
---
## 3. Daily usage: updating progress
1. User opens app.
2. `Splash` quickly forwards to last state; for active users this is `HomeCountdown`.
3. On `HomeCountdown`:
- User sees remaining time and highlevel completion percentage across all goals.
- CTA **Update progress**`GoalsList`.
4. In `GoalsList`:
- Each goal card shows title, image, small progress bar, and possibly remaining time highlight.
- Tap a goal → `GoalDetail`.
5. In `GoalDetail`:
- User marks milestones as done or adjusts progress slider.
- Optional note field for journalstyle comments.
- CTA **Mark goal as completed** when all steps are done.
6. On completion:
- Show lightweight celebration overlay.
- Offer **Share to community** if social features are enabled.
---
## 4. Social and community flows (Phase 2+)
### 4.1 Opting into sharing
- From `Profile` or `Settings > Privacy`:
- Global toggle **Make my profile public** (account visibility: Public / Private).
- Optional privacy explanation sheet before first activation, explaining which data becomes visible.
### 4.2 Browsing the feed
1. Tap **Social** tab.
2. `SocialFeed` shows a list of public milestones and completed goals.
- Cards: user avatar + name, goal title, completion date, days remaining.
3. Tap a card → `PublicMilestoneDetail`.
4. From `PublicMilestoneDetail`:
- CTA **Follow user** → adds follower row.
- View **User profile**`PublicProfile`.
Only users with Public accounts appear in the social feed or can be followed.
### 4.3 Leaderboards
- From `SocialFeed`, user can switch tabs:
- `Feed`, `Leaderboards`.
- `Leaderboards` shows rankings (goals completed, streaks, etc.).
- Tap an entry → `PublicProfile`.
Only users with Public accounts appear on leaderboards.
---
## 5. Settings and notifications
### 5.1 Accessing settings
- From **Profile** tab, button **Settings**`SettingsHome`.
### 5.2 Settings subsections
- `AccountSettings` — email, password, delete account.
- `AppearanceSettings` — light/dark, time format (12/24h).
- `NotificationSettings` — reminder frequency, categories.
- `PrivacySettings` — global public/private account visibility toggle and blocking.
- `AboutChallenge` — reexplain rules and philosophy.
### 5.3 Notification flows
- When user taps a push notification:
- Reminder about time remaining → deep link to `HomeCountdown`.
- Goal progress reminder → deep link to `GoalDetail` for that goal.
- Social notification → deep link to `SocialFeed` or `PublicMilestoneDetail`.
---
## 6. Edge cases and empty states
- **No internet connection**
- Show cached countdown and goals with offline banner.
- Disable actions that require network or queue them.
- **No goals yet (new user)**
- `GoalsList` shows illustrative empty state with CTA **Add your first goal**.
- **Countdown finished**
- `HomeCountdown` switches to a summary view:
- “1356 days completed”
- Stats: goals completed, streaks, favorite milestones.
- CTA to review journey or share a reflection.
This document should be kept in sync with the UI spec and roadmap as features are added or removed.
+226
View File
@@ -0,0 +1,226 @@
# LifeTimer App (1356-Day Countdown) — Project Plan (Supabase Edition)
## 1. Project Overview
**App Name (Working):** LifeTimer
**Concept:**
LifeTimer is a gamified life countdown app. Each user creates a personal bucket list (up to 20 entries), and the 1356-day countdown begins **only after they finalize all goals**. The countdown cannot be stopped, paused, extended, or changed once started.
Users can track their progress, visualize remaining time, and optionally share achievements or milestones with the community. Some bucket list entries may include map locations or images pulled from external APIs.
**Target Platforms:**
* Android (Play Store)
* iOS (App Store)
**Target Audience:**
* Users motivated by time-limited personal challenges.
* People who enjoy gamification, life planning, and social inspiration.
---
## 2. Core Features
### 2.1 User Management
* Sign up / login (email, Google, Apple ID) via Supabase Auth
* User profile: avatar, username, bio
* Online status indicator
### 2.2 1356-Day Countdown
* **Countdown starts only after the user completes their bucket list (up to 20 items)**
* Each user has exactly 1356 days (≈3 years 8 months 11 days)
* Cannot be paused, reset, or modified
* Display as a live countdown (days, hours, minutes, seconds)
* Optional progress bar showing percentage of time passed
### 2.3 Bucket List & Goal Tracking
* Users can add up to **20 bucket list entries**
* Each goal has:
* Title and optional description
* Progress tracker (completed steps or milestones)
* Optional location (integrated with Maps APIs)
* Optional image/media (pulled from input or APIs based on title)
* Goals serve as motivation; they do not affect countdown duration
* Visual indication of goal progress relative to countdown
### 2.4 Social / Online Interaction
* Share milestones or completed goals with other users
* View other users public progress (optional)
* Leaderboards for motivation (e.g., most goals completed within 1356 days)
### 2.5 Notifications
* Countdown reminders (daily, weekly, milestone alerts)
* Achievement & streak notifications
### 2.6 Analytics & Insights
* Daily/weekly stats of goals completed
* Progress visualization vs countdown
* Simple motivational messages based on progress
---
## 3. UI/UX Inspiration
* Clean and minimal UI with light/pastel colors
* Use cards to show goals and countdown progress
* Bottom navigation: Home / Goals / Social / Profile
* Large visual display for countdown (center of home screen)
* Progress bars, charts, or line graphs for milestone tracking
**Suggested Screens:**
1. **Onboarding:** Explanation of the 1356-day challenge, motivational text
2. **Bucket List Creation:**
* Add up to 20 goals
* Optional images and map locations for each entry
* Countdown cannot start until list is complete
3. **Home Screen:**
* Live countdown timer
* Progress circle/bar
* Motivational messages
4. **Goals Screen:** List of bucket list items and progress
5. **Social Screen:** Optional leaderboard or public milestones
6. **Profile Screen:** Avatar, username, start date, days left, achievements
7. **Settings:** Notifications, privacy, theme preferences
---
## 4. Tech Stack
### 4.1 Mobile Framework
* Flutter (cross-platform, beginner-friendly)
### 4.2 Backend / API
* **Supabase**: full backend replacement for Firebase
* **Auth:** Email, Google, Apple ID
* **Database:** PostgreSQL for users, goals, and social interactions
* **Realtime:** Supabase Realtime for live updates (goal progress, social feed)
* **Storage:** Media storage for goal images/videos
* External APIs:
* Maps (Google Maps / OpenStreetMap) for location-based goals
* Image APIs (Unsplash, Pexels, or AI-generated)
### 4.3 State Management
* Flutter: Provider or Riverpod
### 4.4 Notifications
* Local push notifications (Flutter local_notifications package)
* Optional server-side notifications via Supabase Edge Functions
### 4.5 Analytics
* Supabase logs + optional third-party analytics (e.g., Mixpanel)
### 4.6 Design System
* Material 3 / Cupertino widgets
* Custom color themes for progress stages
---
## 5. Database Structure (Supabase PostgreSQL Example)
**Tables:**
```sql
users
id (uuid, primary key)
username
email
avatar_url
bio
countdown_start_date (null until bucket list completed)
countdown_end_date (calculated automatically as start + 1356 days)
created_at
updated_at
goals
id (uuid, primary key)
owner_id (foreign key users.id)
title
description
progress (0-100)
location_lat
location_lng
location_name
image_url
completed (boolean)
created_at
updated_at
followers
id (uuid, primary key)
user_id (foreign key users.id)
follower_id (foreign key users.id)
```
---
## 6. App Architecture
* MVVM / Clean Architecture
* **Model:** User, Goal
* **View:** Screens & Widgets (Flutter UI)
* **ViewModel:** State management (Provider/Riverpod)
* **Repository:** Supabase API calls (Auth, Postgres, Storage)
* Real-time updates: Supabase Realtime for goal progress and social feed
---
## 7. Implementation Roadmap
### Phase 1 — MVP
* User authentication & profile via Supabase
* Bucket list creation (limit 20 goals)
* Countdown starts **after bucket list completion**
* Live countdown and percentage progress
* Add goal progress tracking
### Phase 2 — Social & Motivation
* Share milestones or goals publicly
* View other users progress (optional)
* Leaderboards & achievements
* Push notifications for milestones
### Phase 3 — Advanced Features
* Graphs & charts for progress visualization
* Media uploads & API-based images
* Map integration for location-based goals
* Offline support & caching
* Daily motivational messages
---
## 8. Recommended Tools & Learning Resources
* Flutter: [https://flutter.dev/docs](https://flutter.dev/docs)
* Supabase: [https://supabase.com/docs](https://supabase.com/docs)
* UI Design: Figma / Adobe XD
* State Management: Riverpod tutorials
* Version Control: Git + GitHub
---
## 9. Summary
LifeTimer now uses **Supabase** as a complete backend solution. Users finalize a bucket list of up to 20 goals, then a **fixed 1356-day countdown starts**. Optional images and maps integration make goal tracking engaging. Countdown cannot be stopped or modified, emphasizing commitment, while social and motivational features encourage progress and community support. Flutter + Supabase enables cross-platform support and real-time updates for a dynamic experience.
+84
View File
@@ -0,0 +1,84 @@
# LifeTimer  Functional Requirements
This document expands on project.md and defines functional requirements for the LifeTimer app.
## Legend
- FR x.y = Functional Requirement
## 1. User accounts and profiles
- FR 1.1 Users can sign up and sign in using email and password via Supabase Auth.
- FR 1.2 Users can sign in using Google.
- FR 1.3 Users can sign in using Apple ID on iOS.
- FR 1.4 Users can edit a profile containing avatar, username that is unique, and short bio.
- FR 1.5 The app displays online or last active status where relevant, for example social screens.
- FR 1.6 Users can delete their account and associated data.
## 2. Bucket list and goals
- FR 2.1 Each user can create between 1 and 20 goals in their bucket list. The countdown can only start once at least one goal exists, and no more than 20 goals can ever be created.
- FR 2.2 For each goal, the user can enter title, required, and description, optional.
- FR 2.3 For each goal, the user can optionally attach
- a location, map picker or search,
- one cover image, either uploaded or fetched from an image API based on title.
- FR 2.4 Users can define milestones or steps per goal and mark each step as completed.
- FR 2.5 Users can mark a goal as completed; overall progress is derived from milestones and completion flag.
- FR 2.6 Users can edit or delete a goal until the 1356 day countdown has started.
- FR 2.7 After the countdown has started, goals remain visible but cannot be deleted; limited edits, for example description, may be allowed, but title and count of goals are locked.
## 3. 1356 day countdown
- FR 3.1 The app calculates the countdown start date when the user confirms their bucket list as final.
- FR 3.2 The app stores countdown end date as start date plus 1356 days.
- FR 3.3 The home screen shows a live countdown, days, hours, minutes, seconds.
- FR 3.4 Users can view the percentage of time that has elapsed, visualized via a progress bar or circle.
- FR 3.5 The countdown cannot be paused, reset, or extended by the user.
- FR 3.6 If the end date is in the past, the app shows that the challenge is finished and surfaces summary statistics.
## 4. Progress tracking and insights
- FR 4.1 Users can see a list of goals with per goal progress and overall completion stats.
- FR 4.2 The app shows simple charts or indicators of progress versus remaining time, for example line chart, bar chart, or summary cards.
- FR 4.3 The app shows motivational messages based on the user current progress and remaining time.
## 5. Social and community, optional and opt in
- FR 5.1 Each user can set their overall account visibility to either **Public** or **Private** via a single global toggle.
- FR 5.2 When an account is **Private**, only the owning user can see their goals, milestones, countdown, and statistics. Private accounts never appear in social feeds or leaderboards.
- FR 5.3 When an account is **Public**, other users can see a restricted public view of that user: avatar, username, high level stats, public milestones, and goal summaries, but never email or other sensitive data.
- FR 5.4 Users can follow other users with public accounts and see a feed of their public milestones.
- FR 5.5 Leaderboards surface rankings such as
- most goals completed,
- streak of active days,
- recently completed milestones,
but only for public accounts.
- FR 5.6 Users can unfollow or block other users.
## 6. Notifications
- FR 6.1 Users can configure reminder frequency, daily, weekly, custom, for the countdown.
- FR 6.2 Users receive push or local notifications for
- upcoming milestones or deadlines they set,
- streaks such as three days in a row of updating progress,
- major countdown checkpoints, for example 50 percent or 25 percent time remaining.
- FR 6.3 Users can mute or customize specific notification categories.
- FR 6.4 Notification preferences sync across devices.
## 7. Onboarding and education
- FR 7.1 New users see an onboarding flow explaining the 1356 day challenge and rules.
- FR 7.2 Onboarding includes at least one screen that visually represents the countdown, inspired by the world time style mockups.
- FR 7.3 Users can revisit an About the challenge screen later from Settings.
## 8. Settings
- FR 8.1 Users can switch between light and dark themes.
- FR 8.2 Users can choose 12 hour or 24 hour time formats.
- FR 8.3 Users can update language if localization is implemented.
- FR 8.4 Users can manage privacy and social visibility options.
## 9. Admin and operations, later phase
- FR 9.1 An admin user can moderate reported content and block abusive accounts.
- FR 9.2 Admin tooling may be provided via Supabase dashboard or a separate admin UI.
+48
View File
@@ -0,0 +1,48 @@
# LifeTimer  Non functional Requirements
This document describes quality attributes of the app.
## 1. Performance
- NFR 1.1 The home screen countdown updates smoothly at least once per second without blocking user interactions.
- NFR 1.2 Normal operations should complete within one second on a typical mobile connection when the network is healthy.
- NFR 1.3 Screens should be usable on low end devices with limited memory.
## 2. Reliability and availability
- NFR 2.1 The app should degrade gracefully when offline, caching recent data for read only access.
- NFR 2.2 Operations that change data are queued and retried when the connection is restored, where possible.
- NFR 2.3 Critical data such as goals and countdown timestamps are stored in Supabase and never only on device.
## 3. Security and privacy
- NFR 3.1 All traffic between app and Supabase uses HTTPS.
- NFR 3.2 Access to tables is controlled by Row Level Security policies in Supabase.
- NFR 3.3 Private goals and milestones are not readable by other users.
- NFR 3.4 Authentication tokens are stored securely using platform secure storage.
- NFR 3.5 Data export and deletion can be supported in later phases to comply with privacy expectations.
- NFR 3.6 Supabase service role keys are never embedded in the mobile app; only the public anon key is shipped to clients.
- NFR 3.7 RLS policies are covered by automated tests and code review to prevent accidental data exposure.
- NFR 3.8 Logs and analytics events do not contain secrets (passwords, tokens) or unnecessary personal data.
## 4. Usability and accessibility
- NFR 4.1 App follows platform accessibility guidelines for text size, color contrast, and touch target sizes.
- NFR 4.2 Primary flows are fully usable with screen readers.
- NFR 4.3 Color alone is not the only way to convey critical information.
## 5. Maintainability and scalability
- NFR 5.1 Codebase follows modular feature based structure with clear separation of presentation, state management, and data layers.
- NFR 5.2 Business logic is covered by automated tests at unit or widget level where it brings value.
- NFR 5.3 Supabase schema migrations are version controlled.
## 6. Localization
- NFR 6.1 The app is designed to support multiple languages via Flutter localization.
- NFR 6.2 All user facing strings live in localization files, not hard coded in widgets.
## 7. Analytics and observability
- NFR 7.1 Basic analytics events are sent for key actions such as creating goals, starting the countdown, and completing milestones.
- NFR 7.2 Error logging captures stack traces and anonymized context to help debugging.
+111
View File
@@ -0,0 +1,111 @@
# LifeTimer — Security and Privacy Design
This document describes how LifeTimer should be built to reduce the risk of hacking, data leaks, and misuse.
## 1. Goals
- Protect user data (goals, milestones, countdown, email, profile).
- Make it hard for attackers to access or modify data they do not own.
- Limit damage even if a single part of the system is compromised.
---
## 2. Privacy model
- Each user chooses a single **account visibility** setting:
- **Private account** (default)
- Only the user can see their goals, milestones, countdown, and stats.
- Account never appears in feeds, search, or leaderboards.
- **Public account**
- Other users can see a **limited public view**: avatar, username, high level stats, and public milestones.
- Sensitive data (email, internal IDs, detailed logs) never leaves the backend.
- Visibility is implemented via a boolean flag `users.is_public_profile`.
---
## 3. Access control and Row Level Security (RLS)
High level rules for Supabase/Postgres:
- **General principles**
- Every table that contains user data has RLS enabled.
- There is no unauthenticated read access to private data.
- Service role key is used only in secure server environments, not in the mobile app.
- **users table**
- A user can `SELECT` and `UPDATE` only their own row.
- Other users can `SELECT` a limited subset of columns (username, avatar_url, is_public_profile, high level stats) **only when** `is_public_profile = true`.
- Email and other sensitive fields are never exposed in public queries.
- **goals, goal_steps, activities, notifications tables**
- Reads and writes are allowed only when `owner_id = auth.uid()` (or `user_id = auth.uid()`), regardless of public/private setting.
- Public feeds and leaderboards do **not** expose full goal records; they use derived or aggregated views that expose only non sensitive data.
- **followers and social features**
- Follow relationships can only be created for public accounts.
- Queries that build feeds or leaderboards always join through `users` and require `is_public_profile = true`.
- **Testing RLS**
- For each table, add tests that simulate different users:
- Owner can read/write their rows.
- Other users cannot read private rows.
- Other users can read only allowed fields for public accounts.
---
## 4. Authentication and session security
- Use Supabase Auth for all login and sign up flows.
- Enforce strong password rules for email/password sign up.
- Use OAuth flows (Google, Apple) via official SDKs only.
- Store access and refresh tokens only via platform secure storage APIs.
- Provide logout that clears tokens from secure storage and memory.
- Optionally add app level lock (PIN or biometrics) before opening sensitive screens on a shared device.
---
## 5. Key and configuration management
- Store Supabase keys and environment URLs only in configuration files, not hard coded in code.
- Ship **only** the public anon key in the mobile app.
- Keep the Supabase service role key in server side environments (CI, Edge Functions) and protect it with secret managers.
- Rotate keys if a leak is suspected.
---
## 6. Network and API security
- Enforce HTTPS for all communication with Supabase.
- Optionally enable certificate pinning in the mobile app for additional protection.
- Validate data on the server side (for example inside Edge Functions) for any complex operations.
- Use pagination and rate limiting on endpoints that can be abused for scraping.
---
## 7. Secure coding guidelines
- Use parameterized queries and the official Supabase client instead of building raw queries from strings.
- Never log passwords, tokens, or full request/response bodies that may contain secrets.
- Sanitize and validate user input before using it in business logic.
- Handle errors with generic messages for users and more detailed logs on the backend.
---
## 8. Logging, monitoring, and incident response
- Send errors to a crash/analytics service with anonymized context.
- Monitor for unusual patterns (for example many failed login attempts from one IP).
- Have a procedure for
- disabling compromised accounts,
- rotating keys,
- notifying affected users when needed.
---
## 9. Data lifecycle
- Store only data that is necessary for the product to work.
- Allow users to delete their accounts; deletion should remove or anonymize their data where possible.
- Consider providing data export in later phases so users can keep a copy of their progress.
This document should be kept in sync with `requirements-nonfunctional.md`, `database-schema.md`, and `architecture.md` as the system evolves.
+299
View File
@@ -0,0 +1,299 @@
# LifeTimer Development Timeline
Use this file as a living log of what has been done and what is planned next.
Update it as you work.
Legend
- [ ] planned
- [x] completed
- [~] in progress
## 2026 01 03 - Kickoff
- [x] Defined core concept and Supabase based architecture in project.md.
- [x] Created initial documentation set for requirements, design, architecture, and roadmap.
- [x] Created comprehensive functional and non-functional requirements.
- [x] Defined detailed database schema with RLS policies.
- [x] Specified Flutter project structure and navigation flows.
- [x] Documented UI/UX specifications and security guidelines.
- [ ] Initialize git repository and create .gitignore.
- [ ] Review and complete Flutter project structure.
- [ ] Add required dependencies to pubspec.yaml.
- [ ] Create core theme and routing files.
## Phase 0 - Planning and Foundations (In Progress)
### Project Setup
- [ ] Initialize git repository with proper .gitignore
- [ ] Create initial commit with documentation
- [ ] Review existing Flutter project structure in lifetimer/
- [ ] Verify pubspec.yaml and update dependencies
- [ ] Create environment configuration files (.env.example, .env)
### Core Infrastructure
- [ ] Create bootstrap folder with Supabase client setup
- [ ] Create core theme definitions (light/dark themes)
- [ ] Create app router with all route definitions
- [ ] Create reusable core widgets (buttons, scaffolds, loading indicators)
- [ ] Create error handling and failure types
- [ ] Create utility functions (date/time, validators)
- [ ] Set up state management (Provider/Riverpod)
### Data Layer
- [ ] Create data models (User, Goal, GoalStep, Activity)
- [ ] Create repository interfaces
- [ ] Implement AuthRepository
- [ ] Implement UserRepository
- [ ] Implement GoalsRepository
- [ ] Implement CountdownRepository
- [ ] Implement SocialRepository (Phase 2)
- [ ] Implement NotificationsRepository
### Supabase Setup
- [ ] Create Supabase project
- [ ] Apply database schema migrations
- [ ] Configure RLS policies
- [ ] Set up storage buckets
- [ ] Configure authentication providers (Google, Apple)
- [ ] Test database connections
## Phase 1 - MVP Core Experience
### Authentication Feature
- [ ] Create AuthGate screen
- [ ] Create sign in screen (email/password)
- [ ] Create sign up screen (email/password)
- [ ] Implement Google sign-in
- [ ] Implement Apple sign-in (iOS)
- [ ] Create auth loading screen
- [ ] Implement AuthController
- [ ] Add session management
- [ ] Test auth flows
### Onboarding Feature
- [ ] Create onboarding intro screen
- [ ] Create onboarding how it works screen
- [ ] Create onboarding motivation screen
- [ ] Implement OnboardingController
- [ ] Add onboarding completion tracking
- [ ] Test onboarding flow
### Profile Setup
- [ ] Create profile creation screen
- [ ] Implement avatar upload
- [ ] Add username validation (unique check)
- [ ] Add bio field
- [ ] Test profile setup flow
### Goals Feature
- [ ] Create goals list screen
- [ ] Create goal edit screen
- [ ] Create goal detail screen
- [ ] Implement GoalsController
- [ ] Implement GoalDetailController
- [ ] Add goal title and description fields
- [ ] Add location picker integration
- [ ] Add image upload/API integration
- [ ] Implement milestones/steps per goal
- [ ] Add progress tracking (0-100%)
- [ ] Enforce 1-20 goals limit
- [ ] Add goal completion logic
- [ ] Test goal CRUD operations
### Countdown Feature
- [ ] Create home countdown screen (world time inspired)
- [ ] Implement large countdown display (days, hours, minutes, seconds)
- [ ] Add progress ring/bar showing time elapsed
- [ ] Implement CountdownController
- [ ] Add countdown start confirmation dialog
- [ ] Calculate countdown end date (start + 1356 days)
- [ ] Add motivational messages
- [ ] Test countdown accuracy
- [ ] Test countdown lock (no pause/reset)
### Bucket List Confirmation
- [ ] Create bucket list intro screen
- [ ] Implement confirmation dialog
- [ ] Add countdown start trigger
- [ ] Lock goals after countdown starts
- [ ] Test confirmation flow
### Notifications
- [ ] Set up local notifications
- [ ] Implement daily/weekly reminders
- [ ] Add milestone notifications
- [ ] Add countdown checkpoint notifications (50%, 25% remaining)
- [ ] Create notification settings screen
- [ ] Test notification delivery
### Analytics
- [ ] Set up basic analytics tracking
- [ ] Track key events (goal creation, countdown start, goal completion)
- [ ] Add error logging
- [ ] Test analytics integration
### MVP Testing
- [ ] End-to-end testing of signup → goals → countdown flow
- [ ] Test onboarding completion
- [ ] Test countdown accuracy over time
- [ ] Test goal progress tracking
- [ ] Test notifications
- [ ] Fix critical bugs
- [ ] Prepare for internal testing
## Phase 2 - Social and Motivation
### Following System
- [ ] Implement follow/unfollow functionality
- [ ] Create public profile screens
- [ ] Add follower/following lists
- [ ] Test follow relationships
### Activity Feed
- [ ] Create social feed screen
- [ ] Implement activity tracking
- [ ] Show public milestones in feed
- [ ] Add feed filtering
- [ ] Test feed updates
### Leaderboards
- [ ] Create leaderboards screen
- [ ] Implement goals completed ranking
- [ ] Implement active streak ranking
- [ ] Implement recent milestones ranking
- [ ] Add leaderboard tabs/filters
- [ ] Test leaderboard accuracy
### Achievements
- [ ] Design achievement badges
- [ ] Implement achievement tracking
- [ ] Create achievement notification
- [ ] Add achievements to profile
- [ ] Test achievement unlocks
### Social Notifications
- [ ] Add follow notifications
- [ ] Add milestone share notifications
- [ ] Test social notification delivery
### Phase 2 Testing
- [ ] Test public profile visibility
- [ ] Test social feed privacy
- [ ] Test leaderboards (public accounts only)
- [ ] Test achievements
- [ ] Beta testing with small group
- [ ] Fix bugs and refine features
## Phase 3 - Advanced Experience
### Charts and Insights
- [ ] Create insights screen
- [ ] Implement progress vs time charts
- [ ] Add goal completion trends
- [ ] Implement streak visualization
- [ ] Add summary cards
- [ ] Test chart performance
### Image Integration
- [ ] Integrate Unsplash API
- [ ] Integrate Pexels API
- [ ] Add image search by title
- [ ] Cache images locally
- [ ] Test image loading
### Map Integration
- [ ] Integrate Google Maps API
- [ ] Integrate OpenStreetMap (fallback)
- [ ] Add location picker to goal edit
- [ ] Display location on goal detail
- [ ] Test map functionality
### Offline Support
- [ ] Implement local caching (Hive/SharedPreferences)
- [ ] Cache goals and countdown data
- [ ] Queue offline mutations
- [ ] Sync when connection restored
- [ ] Test offline behavior
### Settings Expansion
- [ ] Add theme switcher (light/dark)
- [ ] Add time format toggle (12/24h)
- [ ] Add language selection
- [ ] Add privacy settings (public/private toggle)
- [ ] Add account deletion
- [ ] Test all settings
### Phase 3 Testing
- [ ] Test charts accuracy
- [ ] Test image API integration
- [ ] Test map functionality
- [ ] Test offline mode
- [ ] Test all settings
- [ ] Performance testing on low-end devices
## Phase 4 - Polish and Release
### Accessibility
- [ ] Review color contrast ratios
- [ ] Test with screen readers
- [ ] Add semantic labels
- [ ] Support dynamic text scaling
- [ ] Fix accessibility issues
### Performance
- [ ] Profile app performance
- [ ] Optimize countdown updates
- [ ] Optimize image loading
- [ ] Reduce app bundle size
- [ ] Test on low-end devices
- [ ] Fix performance bottlenecks
### App Store Preparation
- [ ] Create app store screenshots
- [ ] Write app descriptions
- [ ] Prepare app icons
- [ ] Set up app store listings
- [ ] Configure in-app purchases (if any)
- [ ] Review store guidelines
### Play Store Preparation
- [ ] Create Play Store screenshots
- [ ] Write Play Store descriptions
- [ ] Prepare app icons
- [ ] Set up Play Store listing
- [ ] Review Play Store policies
### Beta Testing
- [ ] Set up beta testing program
- [ ] Recruit beta testers
- [ ] Collect feedback
- [ ] Fix reported bugs
- [ ] Refine features based on feedback
### Release Preparation
- [ ] Final code review
- [ ] Security audit
- [ ] Create release notes
- [ ] Tag release version
- [ ] Deploy to production
### Public Launch
- [ ] Submit to App Store
- [ ] Submit to Play Store
- [ ] Monitor app performance
- [ ] Respond to user reviews
- [ ] Plan post-launch updates
## Later Milestones
- [ ] MVP feature set complete and ready for internal testing
- [ ] Social and leaderboards live in a small beta
- [ ] Advanced charts, maps, and media features deployed
- [ ] Public launch on both app stores
- [ ] Post-launch feature updates and improvements
## Chronological History
- 2026 01 03 - [x] Project kickoff and documentation complete
- 2026 01 03 - [~] Setting up git repository and project structure
+71
View File
@@ -0,0 +1,71 @@
# LifeTimer  UI and UX Specification
This document translates the visual inspiration screenshots into a concrete style for LifeTimer.
## 1. Design principles
- Calm and motivational, not alarming.
- Focus on remaining time and meaningful goals.
- Simple flows that are friendly for non technical users.
## 2. Visual style
Inspiration
- Travel app mockups, card based layout with large imagery and rounded corners for each trip or tour.
- Mood tracking mockups, playful colors, rounded shapes, friendly typography and clear emphasis on current emotional state.
- World time mockups, very large digits and clean layouts that highlight time and locations.
Application to LifeTimer
- Home countdown uses a large typographic layout similar to the world time example, centered on the screen with remaining days as the main focus.
- Bucket list and goals use cards with images similar to the travel example. Each card shows goal title, progress, and remaining time.
- Emotional tone uses gentle pastel colors inspired by the mood app, with clear contrast in dark mode.
## 3. Color and themes
- Primary palette, soft blues and greens for calm, accented with a highlight color for positive actions.
- Light theme, light background with strong contrast for text, subtle shadows for cards.
- Dark theme, very dark background, desaturated colors, neon like highlight accents for countdown and important actions.
- Separate color tokens for success, warning, and neutral informational states.
## 4. Typography
- Use one clean sans serif font family for consistency.
- Large numeric style for the countdown, using extra bold weight.
- Titles and section headers use medium or semibold weight.
- Body text remains highly readable at standard platform sizes with support for dynamic type.
## 5. Layout and components
- Global bottom navigation with four or five destinations
- Home
- Goals
- Social
- Profile
- Optional, Insights
- Reusable components
- Goal card with image, title, location, progress bar, and call to action.
- Progress summary card with small chart and key metrics.
- Primary button with pill shape, full width on narrow screens.
- Chip components for filters such as time range, goal categories, or mood tags.
- Consistent safe area and spacing across all screens.
## 6. Feedback and states
- Loading, skeleton placeholders for cards and lists, not blank screens.
- Empty states, friendly illustration and one clear action, for example Add your first goal.
- Error states, concise error message, optional retry button, and no technical jargon.
- Success, subtle confetti, glow, or color shift when a goal is completed or a big milestone is reached.
## 7. Motion
- Light micro interactions for taps, card selection, and tab transitions.
- Simple transitions between screens, preferably default Flutter transitions with small custom tweaks.
- Avoid heavy or distracting animations on the countdown screen.
## 8. Accessibility
- Minimum contrast ratio respected for text and essential icons.
- Avoid relying only on color for progress; include labels and percentage values.
- Support system text scaling; layouts must not break at larger text sizes.